mirror of
https://github.com/Sonarr/Sonarr.git
synced 2026-03-28 18:04:19 -04:00
Compare commits
2 Commits
v5-develop
...
v5-import-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
003598ce49 | ||
|
|
4de0f7e463 |
@@ -4,7 +4,6 @@ import AppSectionState, {
|
||||
AppSectionListState,
|
||||
AppSectionSaveState,
|
||||
AppSectionSchemaState,
|
||||
PagedAppSectionState,
|
||||
} from 'App/State/AppSectionState';
|
||||
import AutoTagging, { AutoTaggingSpecification } from 'typings/AutoTagging';
|
||||
import CustomFormat from 'typings/CustomFormat';
|
||||
@@ -12,7 +11,6 @@ import CustomFormatSpecification from 'typings/CustomFormatSpecification';
|
||||
import DelayProfile from 'typings/DelayProfile';
|
||||
import DownloadClient from 'typings/DownloadClient';
|
||||
import ImportList from 'typings/ImportList';
|
||||
import ImportListExclusion from 'typings/ImportListExclusion';
|
||||
import ImportListOptionsSettings from 'typings/ImportListOptionsSettings';
|
||||
import DownloadClientOptions from 'typings/Settings/DownloadClientOptions';
|
||||
|
||||
@@ -71,14 +69,6 @@ export interface ImportListOptionsSettingsAppState
|
||||
extends AppSectionItemState<ImportListOptionsSettings>,
|
||||
AppSectionSaveState {}
|
||||
|
||||
export interface ImportListExclusionsSettingsAppState
|
||||
extends AppSectionState<ImportListExclusion>,
|
||||
AppSectionSaveState,
|
||||
PagedAppSectionState,
|
||||
AppSectionDeleteState {
|
||||
pendingChanges: Partial<ImportListExclusion>;
|
||||
}
|
||||
|
||||
interface SettingsAppState {
|
||||
autoTaggings: AutoTaggingAppState;
|
||||
autoTaggingSpecifications: AutoTaggingSpecificationAppState;
|
||||
@@ -87,7 +77,6 @@ interface SettingsAppState {
|
||||
delayProfiles: DelayProfileAppState;
|
||||
downloadClients: DownloadClientAppState;
|
||||
downloadClientOptions: DownloadClientOptionsAppState;
|
||||
importListExclusions: ImportListExclusionsSettingsAppState;
|
||||
importListOptions: ImportListOptionsSettingsAppState;
|
||||
importLists: ImportListAppState;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ interface PageStore {
|
||||
cutoffUnmet: number;
|
||||
events: number;
|
||||
history: number;
|
||||
importListExclusion: number;
|
||||
missing: number;
|
||||
queue: number;
|
||||
}
|
||||
@@ -16,6 +17,7 @@ const pageStore = create<PageStore>(() => ({
|
||||
cutoffUnmet: 1,
|
||||
events: 1,
|
||||
history: 1,
|
||||
importListExclusion: 1,
|
||||
missing: 1,
|
||||
queue: 1,
|
||||
}));
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import React from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||
import EditImportListExclusionModalContent from './EditImportListExclusionModalContent';
|
||||
|
||||
interface EditImportListExclusionModalProps {
|
||||
id?: number;
|
||||
title?: string;
|
||||
tvdbId?: number;
|
||||
isOpen: boolean;
|
||||
onModalClose: () => void;
|
||||
onDeleteImportListExclusionPress?: () => void;
|
||||
@@ -17,22 +17,11 @@ function EditImportListExclusionModal(
|
||||
) {
|
||||
const { isOpen, onModalClose, ...otherProps } = props;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleModalClose = useCallback(() => {
|
||||
dispatch(
|
||||
clearPendingChanges({
|
||||
section: 'settings.importListExclusions',
|
||||
})
|
||||
);
|
||||
onModalClose();
|
||||
}, [dispatch, onModalClose]);
|
||||
|
||||
return (
|
||||
<Modal size={sizes.MEDIUM} isOpen={isOpen} onModalClose={handleModalClose}>
|
||||
<Modal size={sizes.MEDIUM} isOpen={isOpen} onModalClose={onModalClose}>
|
||||
<EditImportListExclusionModalContent
|
||||
{...otherProps}
|
||||
onModalClose={handleModalClose}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
.body {
|
||||
composes: modalBody from '~Components/Modal/ModalBody.css';
|
||||
|
||||
flex: 1 1 430px;
|
||||
}
|
||||
|
||||
.deleteButton {
|
||||
composes: button from '~Components/Link/Button.css';
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'body': string;
|
||||
'deleteButton': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
|
||||
@@ -1,115 +1,70 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
import Alert from 'Components/Alert';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import Button from 'Components/Link/Button';
|
||||
import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import usePrevious from 'Helpers/Hooks/usePrevious';
|
||||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import {
|
||||
saveImportListExclusion,
|
||||
setImportListExclusionValue,
|
||||
} from 'Store/Actions/settingsActions';
|
||||
import selectSettings from 'Store/Selectors/selectSettings';
|
||||
import ImportListExclusion from 'typings/ImportListExclusion';
|
||||
import { InputChanged } from 'typings/inputs';
|
||||
import { PendingSection } from 'typings/pending';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import { useManageImportListExclusion } from './useImportListExclusions';
|
||||
import styles from './EditImportListExclusionModalContent.css';
|
||||
|
||||
const newImportListExclusion = {
|
||||
title: '',
|
||||
tvdbId: 0,
|
||||
};
|
||||
|
||||
function createImportListExclusionSelector(id?: number) {
|
||||
return createSelector(
|
||||
(state: AppState) => state.settings.importListExclusions,
|
||||
(importListExclusions) => {
|
||||
const { isFetching, error, isSaving, saveError, pendingChanges, items } =
|
||||
importListExclusions;
|
||||
|
||||
const mapping = id
|
||||
? items.find((i) => i.id === id)!
|
||||
: newImportListExclusion;
|
||||
const settings = selectSettings(mapping, pendingChanges, saveError);
|
||||
|
||||
return {
|
||||
isFetching,
|
||||
error,
|
||||
isSaving,
|
||||
saveError,
|
||||
item: settings.settings as PendingSection<ImportListExclusion>,
|
||||
...settings,
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
interface EditImportListExclusionModalContentProps {
|
||||
id?: number;
|
||||
title?: string;
|
||||
tvdbId?: number;
|
||||
onModalClose: () => void;
|
||||
onDeleteImportListExclusionPress?: () => void;
|
||||
}
|
||||
|
||||
function EditImportListExclusionModalContent({
|
||||
id,
|
||||
title: existingTitle,
|
||||
tvdbId: existingTvdbId,
|
||||
onModalClose,
|
||||
onDeleteImportListExclusionPress,
|
||||
}: EditImportListExclusionModalContentProps) {
|
||||
const { isFetching, isSaving, item, error, saveError, ...otherProps } =
|
||||
useSelector(createImportListExclusionSelector(id));
|
||||
const {
|
||||
item,
|
||||
isSaving,
|
||||
saveError,
|
||||
validationErrors,
|
||||
validationWarnings,
|
||||
updateValue,
|
||||
save,
|
||||
} = useManageImportListExclusion({
|
||||
id,
|
||||
title: existingTitle,
|
||||
tvdbId: existingTvdbId,
|
||||
});
|
||||
|
||||
const { title, tvdbId } = item;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const previousIsSaving = usePrevious(isSaving);
|
||||
|
||||
const dispatchSetImportListExclusionValue = (payload: {
|
||||
name: string;
|
||||
value: string | number;
|
||||
}) => {
|
||||
// @ts-expect-error 'setImportListExclusionValue' isn't typed yet
|
||||
dispatch(setImportListExclusionValue(payload));
|
||||
};
|
||||
const wasSaving = usePrevious(isSaving);
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) {
|
||||
Object.entries(newImportListExclusion).forEach(([name, value]) => {
|
||||
dispatchSetImportListExclusionValue({ name, value });
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (previousIsSaving && !isSaving && !saveError) {
|
||||
if (wasSaving && !isSaving && !saveError) {
|
||||
onModalClose();
|
||||
}
|
||||
}, [previousIsSaving, isSaving, saveError, onModalClose]);
|
||||
}, [isSaving, wasSaving, saveError, onModalClose]);
|
||||
|
||||
const onSavePress = useCallback(() => {
|
||||
dispatch(saveImportListExclusion({ id }));
|
||||
}, [dispatch, id]);
|
||||
|
||||
const onInputChange = useCallback(
|
||||
(change: InputChanged) => {
|
||||
// @ts-expect-error 'setImportListExclusionValue' isn't typed yet
|
||||
dispatch(setImportListExclusionValue(change));
|
||||
const handleInputChange = useCallback(
|
||||
({ name, value }: InputChanged) => {
|
||||
updateValue(name, value);
|
||||
},
|
||||
[dispatch]
|
||||
[updateValue]
|
||||
);
|
||||
|
||||
const handleSavePress = useCallback(() => {
|
||||
save();
|
||||
}, [save]);
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
@@ -118,42 +73,35 @@ function EditImportListExclusionModalContent({
|
||||
: translate('AddImportListExclusion')}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody className={styles.body}>
|
||||
{isFetching ? <LoadingIndicator /> : null}
|
||||
<ModalBody>
|
||||
<Form
|
||||
validationErrors={validationErrors}
|
||||
validationWarnings={validationWarnings}
|
||||
>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Title')}</FormLabel>
|
||||
|
||||
{!isFetching && error ? (
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('AddImportListExclusionError')}
|
||||
</Alert>
|
||||
) : null}
|
||||
<FormInputGroup
|
||||
type={inputTypes.TEXT}
|
||||
name="title"
|
||||
helpText={translate('SeriesTitleToExcludeHelpText')}
|
||||
{...title}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{!isFetching && !error ? (
|
||||
<Form {...otherProps}>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Title')}</FormLabel>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('TvdbId')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TEXT}
|
||||
name="title"
|
||||
helpText={translate('SeriesTitleToExcludeHelpText')}
|
||||
{...title}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('TvdbId')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.NUMBER}
|
||||
name="tvdbId"
|
||||
helpText={translate('TvdbIdExcludeHelpText')}
|
||||
{...tvdbId}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
) : null}
|
||||
<FormInputGroup
|
||||
type={inputTypes.NUMBER}
|
||||
name="tvdbId"
|
||||
helpText={translate('TvdbIdExcludeHelpText')}
|
||||
{...tvdbId}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
@@ -172,7 +120,7 @@ function EditImportListExclusionModalContent({
|
||||
<SpinnerErrorButton
|
||||
isSpinning={isSaving}
|
||||
error={saveError}
|
||||
onPress={onSavePress}
|
||||
onPress={handleSavePress}
|
||||
>
|
||||
{translate('Save')}
|
||||
</SpinnerErrorButton>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useSelect } from 'App/Select/SelectContext';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
@@ -8,23 +7,30 @@ import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import useModalOpenState from 'Helpers/Hooks/useModalOpenState';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import { deleteImportListExclusion } from 'Store/Actions/Settings/importListExclusions';
|
||||
import ImportListExclusion from 'typings/ImportListExclusion';
|
||||
import { SelectStateInputProps } from 'typings/props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import EditImportListExclusionModal from './EditImportListExclusionModal';
|
||||
import {
|
||||
ImportListExclusion,
|
||||
useDeleteImportListExclusion,
|
||||
} from './useImportListExclusions';
|
||||
import styles from './ImportListExclusionRow.css';
|
||||
|
||||
type ImportListExclusionRowProps = ImportListExclusion;
|
||||
interface ImportListExclusionRowProps extends ImportListExclusion {
|
||||
onModalClose: () => void;
|
||||
}
|
||||
|
||||
function ImportListExclusionRow({
|
||||
id,
|
||||
tvdbId,
|
||||
title,
|
||||
onModalClose,
|
||||
}: ImportListExclusionRowProps) {
|
||||
const { toggleSelected, useIsSelected } = useSelect<ImportListExclusion>();
|
||||
const isSelected = useIsSelected(id);
|
||||
|
||||
const { deleteImportListExclusion } = useDeleteImportListExclusion(id);
|
||||
|
||||
const handleSelectedChange = useCallback(
|
||||
({ id, value, shiftKey = false }: SelectStateInputProps) => {
|
||||
toggleSelected({
|
||||
@@ -36,14 +42,17 @@ function ImportListExclusionRow({
|
||||
[toggleSelected]
|
||||
);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [
|
||||
isEditImportListExclusionModalOpen,
|
||||
setEditImportListExclusionModalOpen,
|
||||
setEditImportListExclusionModalClosed,
|
||||
] = useModalOpenState(false);
|
||||
|
||||
const handleEditModalClose = useCallback(() => {
|
||||
setEditImportListExclusionModalClosed();
|
||||
onModalClose();
|
||||
}, [setEditImportListExclusionModalClosed, onModalClose]);
|
||||
|
||||
const [
|
||||
isDeleteImportListExclusionModalOpen,
|
||||
setDeleteImportListExclusionModalOpen,
|
||||
@@ -51,8 +60,8 @@ function ImportListExclusionRow({
|
||||
] = useModalOpenState(false);
|
||||
|
||||
const handleDeletePress = useCallback(() => {
|
||||
dispatch(deleteImportListExclusion({ id }));
|
||||
}, [id, dispatch]);
|
||||
deleteImportListExclusion();
|
||||
}, [deleteImportListExclusion]);
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
@@ -74,8 +83,10 @@ function ImportListExclusionRow({
|
||||
|
||||
<EditImportListExclusionModal
|
||||
id={id}
|
||||
title={title}
|
||||
tvdbId={tvdbId}
|
||||
isOpen={isEditImportListExclusionModalOpen}
|
||||
onModalClose={setEditImportListExclusionModalClosed}
|
||||
onModalClose={handleEditModalClose}
|
||||
onDeleteImportListExclusionPress={setDeleteImportListExclusionModalOpen}
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { SelectProvider, useSelect } from 'App/Select/SelectContext';
|
||||
import AppState from 'App/State/AppState';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||
@@ -14,21 +11,9 @@ import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import TablePager from 'Components/Table/TablePager';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import usePaging from 'Components/Table/usePaging';
|
||||
import useCurrentPage from 'Helpers/Hooks/useCurrentPage';
|
||||
import useModalOpenState from 'Helpers/Hooks/useModalOpenState';
|
||||
import usePrevious from 'Helpers/Hooks/usePrevious';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import { SortDirection } from 'Helpers/Props/sortDirections';
|
||||
import {
|
||||
bulkDeleteImportListExclusions,
|
||||
clearImportListExclusions,
|
||||
fetchImportListExclusions,
|
||||
gotoImportListExclusionPage,
|
||||
setImportListExclusionSort,
|
||||
setImportListExclusionTableOption,
|
||||
} from 'Store/Actions/Settings/importListExclusions';
|
||||
import ImportListExclusion from 'typings/ImportListExclusion';
|
||||
import { CheckInputChanged } from 'typings/inputs';
|
||||
import { TableOptionsChangePayload } from 'typings/Table';
|
||||
import {
|
||||
@@ -37,7 +22,16 @@ import {
|
||||
} from 'Utilities/pagePopulator';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import EditImportListExclusionModal from './EditImportListExclusionModal';
|
||||
import {
|
||||
setImportListExclusionOption,
|
||||
setImportListExclusionSort,
|
||||
useImportListExclusionOptions,
|
||||
} from './importListExclusionOptionsStore';
|
||||
import ImportListExclusionRow from './ImportListExclusionRow';
|
||||
import useImportListExclusions, {
|
||||
ImportListExclusion,
|
||||
useDeleteImportListExclusions,
|
||||
} from './useImportListExclusions';
|
||||
import styles from './ImportListExclusions.css';
|
||||
|
||||
const COLUMNS: Column[] = [
|
||||
@@ -62,40 +56,27 @@ const COLUMNS: Column[] = [
|
||||
},
|
||||
];
|
||||
|
||||
function createImportListExclusionsSelector() {
|
||||
return createSelector(
|
||||
(state: AppState) => state.settings.importListExclusions,
|
||||
(importListExclusions) => {
|
||||
return {
|
||||
...importListExclusions,
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function ImportListExclusionsContent() {
|
||||
const requestCurrentPage = useCurrentPage();
|
||||
|
||||
const {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
items,
|
||||
pageSize,
|
||||
sortKey,
|
||||
error,
|
||||
sortDirection,
|
||||
page,
|
||||
records,
|
||||
totalPages,
|
||||
totalRecords,
|
||||
isDeleting,
|
||||
deleteError,
|
||||
} = useSelector(createImportListExclusionsSelector());
|
||||
isFetching,
|
||||
isFetched,
|
||||
isLoading,
|
||||
error,
|
||||
page,
|
||||
goToPage,
|
||||
refetch,
|
||||
} = useImportListExclusions();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { pageSize, sortKey, sortDirection } = useImportListExclusionOptions();
|
||||
|
||||
const { deleteImportListExclusions, isDeleting } =
|
||||
useDeleteImportListExclusions();
|
||||
|
||||
const [isConfirmDeleteModalOpen, setIsConfirmDeleteModalOpen] =
|
||||
useState(false);
|
||||
const previousIsDeleting = usePrevious(isDeleting);
|
||||
|
||||
const {
|
||||
allSelected,
|
||||
@@ -119,100 +100,64 @@ function ImportListExclusionsContent() {
|
||||
|
||||
const handleDeleteSelectedPress = useCallback(() => {
|
||||
setIsConfirmDeleteModalOpen(true);
|
||||
}, [setIsConfirmDeleteModalOpen]);
|
||||
}, []);
|
||||
|
||||
const handleDeleteSelectedConfirmed = useCallback(() => {
|
||||
dispatch(bulkDeleteImportListExclusions({ ids: getSelectedIds() }));
|
||||
deleteImportListExclusions({ ids: getSelectedIds() });
|
||||
setIsConfirmDeleteModalOpen(false);
|
||||
}, [getSelectedIds, setIsConfirmDeleteModalOpen, dispatch]);
|
||||
unselectAll();
|
||||
}, [getSelectedIds, deleteImportListExclusions, unselectAll]);
|
||||
|
||||
const handleConfirmDeleteModalClose = useCallback(() => {
|
||||
setIsConfirmDeleteModalOpen(false);
|
||||
}, [setIsConfirmDeleteModalOpen]);
|
||||
|
||||
const {
|
||||
handleFirstPagePress,
|
||||
handlePreviousPagePress,
|
||||
handleNextPagePress,
|
||||
handleLastPagePress,
|
||||
handlePageSelect,
|
||||
} = usePaging({
|
||||
page,
|
||||
totalPages,
|
||||
gotoPage: gotoImportListExclusionPage,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleSortPress = useCallback(
|
||||
(sortKey: string, sortDirection?: SortDirection) => {
|
||||
dispatch(setImportListExclusionSort({ sortKey, sortDirection }));
|
||||
setImportListExclusionSort({ sortKey, sortDirection });
|
||||
},
|
||||
[dispatch]
|
||||
[]
|
||||
);
|
||||
|
||||
const handleTableOptionChange = useCallback(
|
||||
(payload: TableOptionsChangePayload) => {
|
||||
dispatch(setImportListExclusionTableOption(payload));
|
||||
|
||||
if (payload.pageSize) {
|
||||
dispatch(gotoImportListExclusionPage({ page: 1 }));
|
||||
setImportListExclusionOption('pageSize', payload.pageSize as number);
|
||||
goToPage(1);
|
||||
}
|
||||
},
|
||||
[dispatch]
|
||||
[goToPage]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (requestCurrentPage) {
|
||||
dispatch(fetchImportListExclusions());
|
||||
} else {
|
||||
dispatch(gotoImportListExclusionPage({ page: 1 }));
|
||||
}
|
||||
|
||||
return () => {
|
||||
dispatch(clearImportListExclusions());
|
||||
};
|
||||
}, [requestCurrentPage, dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
const repopulate = () => {
|
||||
dispatch(fetchImportListExclusions());
|
||||
};
|
||||
|
||||
registerPagePopulator(repopulate);
|
||||
|
||||
return () => {
|
||||
unregisterPagePopulator(repopulate);
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (previousIsDeleting && !isDeleting && !deleteError) {
|
||||
unselectAll();
|
||||
|
||||
dispatch(fetchImportListExclusions());
|
||||
}
|
||||
}, [
|
||||
previousIsDeleting,
|
||||
isDeleting,
|
||||
deleteError,
|
||||
items,
|
||||
dispatch,
|
||||
unselectAll,
|
||||
]);
|
||||
|
||||
const [
|
||||
isAddImportListExclusionModalOpen,
|
||||
setAddImportListExclusionModalOpen,
|
||||
setAddImportListExclusionModalClosed,
|
||||
] = useModalOpenState(false);
|
||||
|
||||
const isFetchingForFirstTime = isFetching && !isPopulated;
|
||||
const handleAddModalClose = useCallback(() => {
|
||||
setAddImportListExclusionModalClosed();
|
||||
refetch();
|
||||
}, [setAddImportListExclusionModalClosed, refetch]);
|
||||
|
||||
useEffect(() => {
|
||||
const repopulate = () => {
|
||||
refetch();
|
||||
};
|
||||
|
||||
registerPagePopulator(repopulate);
|
||||
|
||||
return () => {
|
||||
unregisterPagePopulator(repopulate);
|
||||
};
|
||||
}, [refetch]);
|
||||
|
||||
return (
|
||||
<FieldSet legend={translate('ImportListExclusions')}>
|
||||
<PageSectionContent
|
||||
errorMessage={translate('ImportListExclusionsLoadError')}
|
||||
isFetching={isFetchingForFirstTime}
|
||||
isPopulated={isPopulated}
|
||||
isFetching={isLoading && !isFetched}
|
||||
isPopulated={isFetched}
|
||||
error={error}
|
||||
>
|
||||
<Table
|
||||
@@ -229,8 +174,14 @@ function ImportListExclusionsContent() {
|
||||
onSortPress={handleSortPress}
|
||||
>
|
||||
<TableBody>
|
||||
{items.map((item) => {
|
||||
return <ImportListExclusionRow key={item.id} {...item} />;
|
||||
{records.map((item) => {
|
||||
return (
|
||||
<ImportListExclusionRow
|
||||
key={item.id}
|
||||
{...item}
|
||||
onModalClose={refetch}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
<TableRow>
|
||||
@@ -260,16 +211,12 @@ function ImportListExclusionsContent() {
|
||||
totalPages={totalPages}
|
||||
totalRecords={totalRecords}
|
||||
isFetching={isFetching}
|
||||
onFirstPagePress={handleFirstPagePress}
|
||||
onPreviousPagePress={handlePreviousPagePress}
|
||||
onNextPagePress={handleNextPagePress}
|
||||
onLastPagePress={handleLastPagePress}
|
||||
onPageSelect={handlePageSelect}
|
||||
onPageSelect={goToPage}
|
||||
/>
|
||||
|
||||
<EditImportListExclusionModal
|
||||
isOpen={isAddImportListExclusionModalOpen}
|
||||
onModalClose={setAddImportListExclusionModalClosed}
|
||||
onModalClose={handleAddModalClose}
|
||||
/>
|
||||
|
||||
<ConfirmModal
|
||||
@@ -287,10 +234,10 @@ function ImportListExclusionsContent() {
|
||||
}
|
||||
|
||||
function ImportListExclusions() {
|
||||
const { items } = useSelector(createImportListExclusionsSelector());
|
||||
const { records } = useImportListExclusions();
|
||||
|
||||
return (
|
||||
<SelectProvider items={items}>
|
||||
<SelectProvider<ImportListExclusion> items={records}>
|
||||
<ImportListExclusionsContent />
|
||||
</SelectProvider>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { createOptionsStore } from 'Helpers/Hooks/useOptionsStore';
|
||||
import { SortDirection } from 'Helpers/Props/sortDirections';
|
||||
|
||||
export interface ImportListExclusionOptions {
|
||||
pageSize: number;
|
||||
sortKey: string;
|
||||
sortDirection: SortDirection;
|
||||
}
|
||||
|
||||
const { useOptions, setOptions, setOption, setSort } =
|
||||
createOptionsStore<ImportListExclusionOptions>(
|
||||
'import_list_exclusion_options',
|
||||
() => {
|
||||
return {
|
||||
pageSize: 20,
|
||||
sortKey: 'id',
|
||||
sortDirection: 'descending',
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export const useImportListExclusionOptions = useOptions;
|
||||
export const setImportListExclusionOptions = setOptions;
|
||||
export const setImportListExclusionOption = setOption;
|
||||
export const setImportListExclusionSort = setSort;
|
||||
@@ -0,0 +1,163 @@
|
||||
import { keepPreviousData, useQueryClient } from '@tanstack/react-query';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import ModelBase from 'App/ModelBase';
|
||||
import useApiMutation from 'Helpers/Hooks/useApiMutation';
|
||||
import usePage from 'Helpers/Hooks/usePage';
|
||||
import usePagedApiQuery from 'Helpers/Hooks/usePagedApiQuery';
|
||||
import { usePendingChangesStore } from 'Helpers/Hooks/usePendingChangesStore';
|
||||
import selectSettings from 'Store/Selectors/selectSettings';
|
||||
import { useImportListExclusionOptions } from './importListExclusionOptionsStore';
|
||||
|
||||
export interface ImportListExclusion extends ModelBase {
|
||||
tvdbId: number;
|
||||
title: string;
|
||||
}
|
||||
|
||||
const PATH = '/importlistexclusion';
|
||||
|
||||
const NEW_IMPORT_LIST_EXCLUSION = {
|
||||
title: '',
|
||||
tvdbId: 0,
|
||||
};
|
||||
|
||||
interface BulkImportListExclusionData {
|
||||
ids: number[];
|
||||
}
|
||||
|
||||
const useImportListExclusions = () => {
|
||||
const { page, goToPage } = usePage('importListExclusion');
|
||||
const { pageSize, sortKey, sortDirection } = useImportListExclusionOptions();
|
||||
|
||||
const { refetch, ...query } = usePagedApiQuery<ImportListExclusion>({
|
||||
path: PATH,
|
||||
page,
|
||||
pageSize,
|
||||
sortKey,
|
||||
sortDirection,
|
||||
queryOptions: {
|
||||
placeholderData: keepPreviousData,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
...query,
|
||||
goToPage,
|
||||
page,
|
||||
refetch,
|
||||
};
|
||||
};
|
||||
|
||||
export default useImportListExclusions;
|
||||
|
||||
interface ManageImportListExclusionOptions {
|
||||
id?: number;
|
||||
title?: string;
|
||||
tvdbId?: number;
|
||||
}
|
||||
|
||||
export const useManageImportListExclusion = ({
|
||||
id,
|
||||
title,
|
||||
tvdbId,
|
||||
}: ManageImportListExclusionOptions) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const item = useMemo(() => {
|
||||
return id
|
||||
? { title: title ?? '', tvdbId: tvdbId ?? 0 }
|
||||
: NEW_IMPORT_LIST_EXCLUSION;
|
||||
}, [id, title, tvdbId]);
|
||||
|
||||
const { pendingChanges, setPendingChange } =
|
||||
usePendingChangesStore<ImportListExclusion>({});
|
||||
|
||||
const {
|
||||
mutate,
|
||||
isPending: isSaving,
|
||||
error: saveError,
|
||||
} = useApiMutation<ImportListExclusion, ImportListExclusion>({
|
||||
path: id ? `${PATH}/${id}` : PATH,
|
||||
method: id ? 'PUT' : 'POST',
|
||||
mutationOptions: {
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: [PATH] });
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { settings, validationErrors, validationWarnings } = useMemo(() => {
|
||||
return selectSettings(item, pendingChanges, saveError);
|
||||
}, [item, pendingChanges, saveError]);
|
||||
|
||||
const updateValue = useCallback(
|
||||
(name: string, value: unknown) => {
|
||||
// @ts-expect-error - name is not yet typed
|
||||
setPendingChange(name, value);
|
||||
},
|
||||
[setPendingChange]
|
||||
);
|
||||
|
||||
const save = useCallback(() => {
|
||||
const payload = {
|
||||
...item,
|
||||
...pendingChanges,
|
||||
} as ImportListExclusion;
|
||||
|
||||
if (id) {
|
||||
payload.id = id;
|
||||
}
|
||||
|
||||
mutate(payload);
|
||||
}, [id, item, pendingChanges, mutate]);
|
||||
|
||||
return {
|
||||
item: settings,
|
||||
isSaving,
|
||||
saveError,
|
||||
validationErrors,
|
||||
validationWarnings,
|
||||
updateValue,
|
||||
save,
|
||||
};
|
||||
};
|
||||
|
||||
export const useDeleteImportListExclusion = (id: number) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { mutate, isPending } = useApiMutation<unknown, void>({
|
||||
path: `${PATH}/${id}`,
|
||||
method: 'DELETE',
|
||||
mutationOptions: {
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: [PATH] });
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
deleteImportListExclusion: mutate,
|
||||
isDeleting: isPending,
|
||||
};
|
||||
};
|
||||
|
||||
export const useDeleteImportListExclusions = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { mutate, isPending } = useApiMutation<
|
||||
unknown,
|
||||
BulkImportListExclusionData
|
||||
>({
|
||||
path: `${PATH}/bulk`,
|
||||
method: 'DELETE',
|
||||
mutationOptions: {
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: [PATH] });
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
deleteImportListExclusions: mutate,
|
||||
isDeleting: isPending,
|
||||
};
|
||||
};
|
||||
@@ -1,110 +0,0 @@
|
||||
import { createAction } from 'redux-actions';
|
||||
import createBulkRemoveItemHandler from 'Store/Actions/Creators/createBulkRemoveItemHandler';
|
||||
import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHandler';
|
||||
import createSaveProviderHandler from 'Store/Actions/Creators/createSaveProviderHandler';
|
||||
import createServerSideCollectionHandlers from 'Store/Actions/Creators/createServerSideCollectionHandlers';
|
||||
import createClearReducer from 'Store/Actions/Creators/Reducers/createClearReducer';
|
||||
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
|
||||
import createSetTableOptionReducer from 'Store/Actions/Creators/Reducers/createSetTableOptionReducer';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import serverSideCollectionHandlers from 'Utilities/State/serverSideCollectionHandlers';
|
||||
|
||||
//
|
||||
// Variables
|
||||
|
||||
const section = 'settings.importListExclusions';
|
||||
|
||||
//
|
||||
// Actions Types
|
||||
|
||||
export const FETCH_IMPORT_LIST_EXCLUSIONS = 'settings/importListExclusions/fetchImportListExclusions';
|
||||
export const GOTO_IMPORT_LIST_EXCLUSION_PAGE = 'settings/importListExclusions/gotoImportListExclusionPage';
|
||||
export const SET_IMPORT_LIST_EXCLUSION_SORT = 'settings/importListExclusions/setImportListExclusionSort';
|
||||
export const SAVE_IMPORT_LIST_EXCLUSION = 'settings/importListExclusions/saveImportListExclusion';
|
||||
export const DELETE_IMPORT_LIST_EXCLUSION = 'settings/importListExclusions/deleteImportListExclusion';
|
||||
export const BULK_DELETE_IMPORT_LIST_EXCLUSIONS = 'settings/importListExclusions/bulkDeleteImportListExclusions';
|
||||
export const CLEAR_IMPORT_LIST_EXCLUSIONS = 'settings/importListExclusions/clearImportListExclusions';
|
||||
|
||||
export const SET_IMPORT_LIST_EXCLUSION_TABLE_OPTION = 'settings/importListExclusions/setImportListExclusionTableOption';
|
||||
export const SET_IMPORT_LIST_EXCLUSION_VALUE = 'settings/importListExclusions/setImportListExclusionValue';
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
||||
export const fetchImportListExclusions = createThunk(FETCH_IMPORT_LIST_EXCLUSIONS);
|
||||
export const gotoImportListExclusionPage = createThunk(GOTO_IMPORT_LIST_EXCLUSION_PAGE);
|
||||
export const setImportListExclusionSort = createThunk(SET_IMPORT_LIST_EXCLUSION_SORT);
|
||||
export const saveImportListExclusion = createThunk(SAVE_IMPORT_LIST_EXCLUSION);
|
||||
export const deleteImportListExclusion = createThunk(DELETE_IMPORT_LIST_EXCLUSION);
|
||||
export const bulkDeleteImportListExclusions = createThunk(BULK_DELETE_IMPORT_LIST_EXCLUSIONS);
|
||||
export const clearImportListExclusions = createAction(CLEAR_IMPORT_LIST_EXCLUSIONS);
|
||||
|
||||
export const setImportListExclusionTableOption = createAction(SET_IMPORT_LIST_EXCLUSION_TABLE_OPTION);
|
||||
export const setImportListExclusionValue = createAction(SET_IMPORT_LIST_EXCLUSION_VALUE, (payload) => {
|
||||
return {
|
||||
section,
|
||||
...payload
|
||||
};
|
||||
});
|
||||
|
||||
//
|
||||
// Details
|
||||
|
||||
export default {
|
||||
|
||||
//
|
||||
// State
|
||||
|
||||
defaultState: {
|
||||
isFetching: false,
|
||||
isPopulated: false,
|
||||
error: null,
|
||||
pageSize: 20,
|
||||
items: [],
|
||||
isSaving: false,
|
||||
saveError: null,
|
||||
isDeleting: false,
|
||||
deleteError: null,
|
||||
pendingChanges: {}
|
||||
},
|
||||
|
||||
//
|
||||
// Action Handlers
|
||||
|
||||
actionHandlers: handleThunks({
|
||||
...createServerSideCollectionHandlers(
|
||||
section,
|
||||
'/importlistexclusion/paged',
|
||||
fetchImportListExclusions,
|
||||
{
|
||||
[serverSideCollectionHandlers.FETCH]: FETCH_IMPORT_LIST_EXCLUSIONS,
|
||||
[serverSideCollectionHandlers.EXACT_PAGE]: GOTO_IMPORT_LIST_EXCLUSION_PAGE,
|
||||
[serverSideCollectionHandlers.SORT]: SET_IMPORT_LIST_EXCLUSION_SORT
|
||||
}
|
||||
),
|
||||
[SAVE_IMPORT_LIST_EXCLUSION]: createSaveProviderHandler(section, '/importlistexclusion'),
|
||||
[DELETE_IMPORT_LIST_EXCLUSION]: createRemoveItemHandler(section, '/importlistexclusion'),
|
||||
[BULK_DELETE_IMPORT_LIST_EXCLUSIONS]: createBulkRemoveItemHandler(section, '/importlistexclusion/bulk')
|
||||
}),
|
||||
|
||||
//
|
||||
// Reducers
|
||||
|
||||
reducers: {
|
||||
[SET_IMPORT_LIST_EXCLUSION_VALUE]: createSetSettingValueReducer(section),
|
||||
[SET_IMPORT_LIST_EXCLUSION_TABLE_OPTION]: createSetTableOptionReducer(section),
|
||||
|
||||
[CLEAR_IMPORT_LIST_EXCLUSIONS]: createClearReducer(section, {
|
||||
isFetching: false,
|
||||
isPopulated: false,
|
||||
error: null,
|
||||
items: [],
|
||||
isDeleting: false,
|
||||
deleteError: null,
|
||||
pendingChanges: {},
|
||||
totalPages: 0,
|
||||
totalRecords: 0
|
||||
})
|
||||
}
|
||||
|
||||
};
|
||||
@@ -7,7 +7,6 @@ import customFormatSpecifications from './Settings/customFormatSpecifications';
|
||||
import delayProfiles from './Settings/delayProfiles';
|
||||
import downloadClientOptions from './Settings/downloadClientOptions';
|
||||
import downloadClients from './Settings/downloadClients';
|
||||
import importListExclusions from './Settings/importListExclusions';
|
||||
import importListOptions from './Settings/importListOptions';
|
||||
import importLists from './Settings/importLists';
|
||||
|
||||
@@ -20,7 +19,6 @@ export * from './Settings/downloadClients';
|
||||
export * from './Settings/downloadClientOptions';
|
||||
export * from './Settings/importListOptions';
|
||||
export * from './Settings/importLists';
|
||||
export * from './Settings/importListExclusions';
|
||||
|
||||
//
|
||||
// Variables
|
||||
@@ -40,7 +38,6 @@ export const defaultState = {
|
||||
downloadClients: downloadClients.defaultState,
|
||||
downloadClientOptions: downloadClientOptions.defaultState,
|
||||
importLists: importLists.defaultState,
|
||||
importListExclusions: importListExclusions.defaultState,
|
||||
importListOptions: importListOptions.defaultState
|
||||
};
|
||||
|
||||
@@ -60,7 +57,6 @@ export const actionHandlers = handleThunks({
|
||||
...downloadClients.actionHandlers,
|
||||
...downloadClientOptions.actionHandlers,
|
||||
...importLists.actionHandlers,
|
||||
...importListExclusions.actionHandlers,
|
||||
...importListOptions.actionHandlers
|
||||
});
|
||||
|
||||
@@ -76,7 +72,6 @@ export const reducers = createHandleActions({
|
||||
...downloadClients.reducers,
|
||||
...downloadClientOptions.reducers,
|
||||
...importLists.reducers,
|
||||
...importListExclusions.reducers,
|
||||
...importListOptions.reducers
|
||||
|
||||
}, defaultState, section);
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import ModelBase from 'App/ModelBase';
|
||||
|
||||
export default interface ImportListExclusion extends ModelBase {
|
||||
tvdbId: number;
|
||||
title: string;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Sonarr.Api.V5.ImportLists;
|
||||
|
||||
public class ImportListExclusionBulkResource
|
||||
{
|
||||
public required HashSet<int> Ids { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.ImportLists.Exclusions;
|
||||
using Sonarr.Http;
|
||||
using Sonarr.Http.Extensions;
|
||||
using Sonarr.Http.REST;
|
||||
using Sonarr.Http.REST.Attributes;
|
||||
|
||||
namespace Sonarr.Api.V5.ImportLists;
|
||||
|
||||
[V5ApiController]
|
||||
public class ImportListExclusionController : RestController<ImportListExclusionResource>
|
||||
{
|
||||
private readonly IImportListExclusionService _importListExclusionService;
|
||||
|
||||
public ImportListExclusionController(IImportListExclusionService importListExclusionService,
|
||||
ImportListExclusionExistsValidator importListExclusionExistsValidator)
|
||||
{
|
||||
_importListExclusionService = importListExclusionService;
|
||||
|
||||
SharedValidator.RuleFor(c => c.TvdbId).Cascade(CascadeMode.Stop)
|
||||
.NotEmpty()
|
||||
.SetValidator(importListExclusionExistsValidator);
|
||||
|
||||
SharedValidator.RuleFor(c => c.Title).NotEmpty();
|
||||
}
|
||||
|
||||
protected override ImportListExclusionResource GetResourceById(int id)
|
||||
{
|
||||
return _importListExclusionService.Get(id).ToResource();
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Produces("application/json")]
|
||||
public PagingResource<ImportListExclusionResource> GetImportListExclusions([FromQuery] PagingRequestResource paging)
|
||||
{
|
||||
var pagingResource = new PagingResource<ImportListExclusionResource>(paging);
|
||||
var pageSpec = pagingResource.MapToPagingSpec<ImportListExclusionResource, ImportListExclusion>(
|
||||
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"id",
|
||||
"title",
|
||||
"tvdbId"
|
||||
},
|
||||
"id",
|
||||
SortDirection.Descending);
|
||||
|
||||
return pageSpec.ApplyToPage(_importListExclusionService.Paged, ImportListExclusionResourceMapper.ToResource);
|
||||
}
|
||||
|
||||
[RestPostById]
|
||||
[Consumes("application/json")]
|
||||
public ActionResult<ImportListExclusionResource> AddImportListExclusion([FromBody] ImportListExclusionResource resource)
|
||||
{
|
||||
var importListExclusion = _importListExclusionService.Add(resource.ToModel());
|
||||
|
||||
return Created(importListExclusion.Id);
|
||||
}
|
||||
|
||||
[RestPutById]
|
||||
[Consumes("application/json")]
|
||||
public ActionResult<ImportListExclusionResource> UpdateImportListExclusion([FromBody] ImportListExclusionResource resource)
|
||||
{
|
||||
_importListExclusionService.Update(resource.ToModel());
|
||||
|
||||
return Accepted(resource.Id);
|
||||
}
|
||||
|
||||
[RestDeleteById]
|
||||
public ActionResult DeleteImportListExclusion(int id)
|
||||
{
|
||||
_importListExclusionService.Delete(id);
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpDelete("bulk")]
|
||||
[Consumes("application/json")]
|
||||
public ActionResult DeleteImportListExclusions([FromBody] ImportListExclusionBulkResource resource)
|
||||
{
|
||||
_importListExclusionService.Delete(resource.Ids.ToList());
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using FluentValidation.Validators;
|
||||
using NzbDrone.Core.ImportLists.Exclusions;
|
||||
|
||||
namespace Sonarr.Api.V5.ImportLists;
|
||||
|
||||
public class ImportListExclusionExistsValidator : PropertyValidator
|
||||
{
|
||||
private readonly IImportListExclusionService _importListExclusionService;
|
||||
|
||||
public ImportListExclusionExistsValidator(IImportListExclusionService importListExclusionService)
|
||||
{
|
||||
_importListExclusionService = importListExclusionService;
|
||||
}
|
||||
|
||||
protected override string GetDefaultMessageTemplate() => "This exclusion has already been added.";
|
||||
|
||||
protected override bool IsValid(PropertyValidatorContext context)
|
||||
{
|
||||
if (context.PropertyValue == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (context.InstanceToValidate is not ImportListExclusionResource listExclusionResource)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return !_importListExclusionService.All().Exists(v => v.TvdbId == (int)context.PropertyValue && v.Id != listExclusionResource.Id);
|
||||
}
|
||||
}
|
||||
38
src/Sonarr.Api.V5/ImportLists/ImportListExclusionResource.cs
Normal file
38
src/Sonarr.Api.V5/ImportLists/ImportListExclusionResource.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using NzbDrone.Core.ImportLists.Exclusions;
|
||||
using Sonarr.Http.REST;
|
||||
|
||||
namespace Sonarr.Api.V5.ImportLists;
|
||||
|
||||
public class ImportListExclusionResource : RestResource
|
||||
{
|
||||
public int TvdbId { get; set; }
|
||||
public string? Title { get; set; }
|
||||
}
|
||||
|
||||
public static class ImportListExclusionResourceMapper
|
||||
{
|
||||
public static ImportListExclusionResource ToResource(this ImportListExclusion model)
|
||||
{
|
||||
return new ImportListExclusionResource
|
||||
{
|
||||
Id = model.Id,
|
||||
TvdbId = model.TvdbId,
|
||||
Title = model.Title,
|
||||
};
|
||||
}
|
||||
|
||||
public static ImportListExclusion ToModel(this ImportListExclusionResource resource)
|
||||
{
|
||||
return new ImportListExclusion
|
||||
{
|
||||
Id = resource.Id,
|
||||
TvdbId = resource.TvdbId,
|
||||
Title = resource.Title
|
||||
};
|
||||
}
|
||||
|
||||
public static List<ImportListExclusionResource> ToResource(this IEnumerable<ImportListExclusion> models)
|
||||
{
|
||||
return models.Select(ToResource).ToList();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user