New: Import list exclusion pagination and bulk removal

(cherry picked from commit 428569106499b5e3a463f1990ae2996d1ae4ab49)

Persist page size for Import List Exclusions

(cherry picked from commit e81bb3b993adac705fd61dc9e281b040ca2338f5)

Clear pending changes for edit import list exclusions on modal close

(cherry picked from commit 7b87de2e93c2aa499cff224f84253ba944bb58d4)

Fixed actions column width for import list exclusions

(cherry picked from commit d691ad8e12ea4f2bc77f0b551c17d22d91c4ba22)
pull/10287/head
The Dark 9 months ago committed by Qstick
parent b1a7652753
commit b6d9c73a17

@ -18,7 +18,10 @@ export interface AppSectionSaveState {
}
export interface PagedAppSectionState {
page: number;
pageSize: number;
totalPages: number;
totalRecords?: number;
}
export interface AppSectionFilterState<T> {
@ -38,6 +41,7 @@ export interface AppSectionItemState<T> {
isFetching: boolean;
isPopulated: boolean;
error: Error;
pendingChanges: Partial<T>;
item: T;
}

@ -3,10 +3,12 @@ import AppSectionState, {
AppSectionItemState,
AppSectionSaveState,
AppSectionSchemaState,
PagedAppSectionState,
} from 'App/State/AppSectionState';
import Language from 'Language/Language';
import DownloadClient from 'typings/DownloadClient';
import ImportList from 'typings/ImportList';
import ImportListExclusion from 'typings/ImportListExclusion';
import ImportListOptionsSettings from 'typings/ImportListOptionsSettings';
import Indexer from 'typings/Indexer';
import IndexerFlag from 'typings/IndexerFlag';
@ -41,6 +43,14 @@ export interface ImportListOptionsSettingsAppState
extends AppSectionItemState<ImportListOptionsSettings>,
AppSectionSaveState {}
export interface ImportListExclusionsSettingsAppState
extends AppSectionState<ImportListExclusion>,
AppSectionSaveState,
PagedAppSectionState,
AppSectionDeleteState {
pendingChanges: Partial<ImportListExclusion>;
}
export type IndexerFlagSettingsAppState = AppSectionState<IndexerFlag>;
export type LanguageSettingsAppState = AppSectionState<Language>;
export type UiSettingsAppState = AppSectionItemState<UiSettings>;
@ -48,6 +58,7 @@ export type UiSettingsAppState = AppSectionItemState<UiSettings>;
interface SettingsAppState {
advancedSettings: boolean;
downloadClients: DownloadClientAppState;
importListExclusions: ImportListExclusionsSettingsAppState;
importListOptions: ImportListOptionsSettingsAppState;
importLists: ImportListAppState;
indexerFlags: IndexerFlagSettingsAppState;

@ -5,6 +5,7 @@ type PropertyFunction<T> = () => T;
interface Column {
name: string;
label: string | PropertyFunction<string> | React.ReactNode;
className?: string;
columnLabel?: string;
isSortable?: boolean;
isVisible: boolean;

@ -66,7 +66,9 @@ function Table(props) {
columns.map((column) => {
const {
name,
isVisible
isVisible,
isSortable,
...otherColumnProps
} = column;
if (!isVisible) {
@ -84,6 +86,7 @@ function Table(props) {
name={name}
isSortable={false}
{...otherProps}
{...otherColumnProps}
>
<TableOptionsModalWrapper
columns={columns}

@ -0,0 +1,54 @@
import { useCallback, useMemo } from 'react';
import { useDispatch } from 'react-redux';
interface PagingOptions {
page: number;
totalPages: number;
gotoPage: ({ page }: { page: number }) => void;
}
function usePaging(options: PagingOptions) {
const { page, totalPages, gotoPage } = options;
const dispatch = useDispatch();
const handleFirstPagePress = useCallback(() => {
dispatch(gotoPage({ page: 1 }));
}, [dispatch, gotoPage]);
const handlePreviousPagePress = useCallback(() => {
dispatch(gotoPage({ page: Math.max(page - 1, 1) }));
}, [page, dispatch, gotoPage]);
const handleNextPagePress = useCallback(() => {
dispatch(gotoPage({ page: Math.min(page + 1, totalPages) }));
}, [page, totalPages, dispatch, gotoPage]);
const handleLastPagePress = useCallback(() => {
dispatch(gotoPage({ page: totalPages }));
}, [totalPages, dispatch, gotoPage]);
const handlePageSelect = useCallback(
(page: number) => {
dispatch(gotoPage({ page }));
},
[dispatch, gotoPage]
);
return useMemo(() => {
return {
handleFirstPagePress,
handlePreviousPagePress,
handleNextPagePress,
handleLastPagePress,
handlePageSelect,
};
}, [
handleFirstPagePress,
handlePreviousPagePress,
handleNextPagePress,
handleLastPagePress,
handlePageSelect,
]);
}
export default usePaging;

@ -0,0 +1,9 @@
import { useHistory } from 'react-router-dom';
function useCurrentPage() {
const history = useHistory();
return history.action === 'POP';
}
export default useCurrentPage;

@ -1,27 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props';
import EditImportListExclusionModalContentConnector from './EditImportListExclusionModalContentConnector';
function EditImportListExclusionModal({ isOpen, onModalClose, ...otherProps }) {
return (
<Modal
size={sizes.MEDIUM}
isOpen={isOpen}
onModalClose={onModalClose}
>
<EditImportListExclusionModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
</Modal>
);
}
EditImportListExclusionModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default EditImportListExclusionModal;

@ -0,0 +1,41 @@
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
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;
isOpen: boolean;
onModalClose: () => void;
onDeleteImportListExclusionPress?: () => void;
}
function EditImportListExclusionModal(
props: EditImportListExclusionModalProps
) {
const { isOpen, onModalClose, ...otherProps } = props;
const dispatch = useDispatch();
const onModalClosePress = useCallback(() => {
dispatch(
clearPendingChanges({
section: 'settings.importListExclusions',
})
);
onModalClose();
}, [dispatch, onModalClose]);
return (
<Modal size={sizes.MEDIUM} isOpen={isOpen} onModalClose={onModalClosePress}>
<EditImportListExclusionModalContent
{...otherProps}
onModalClose={onModalClosePress}
/>
</Modal>
);
}
export default EditImportListExclusionModal;

@ -1,43 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { clearPendingChanges } from 'Store/Actions/baseActions';
import EditImportListExclusionModal from './EditImportListExclusionModal';
function mapStateToProps() {
return {};
}
const mapDispatchToProps = {
clearPendingChanges
};
class EditImportListExclusionModalConnector extends Component {
//
// Listeners
onModalClose = () => {
this.props.clearPendingChanges({ section: 'settings.importListExclusions' });
this.props.onModalClose();
};
//
// Render
render() {
return (
<EditImportListExclusionModal
{...this.props}
onModalClose={this.onModalClose}
/>
);
}
}
EditImportListExclusionModalConnector.propTypes = {
onModalClose: PropTypes.func.isRequired,
clearPendingChanges: PropTypes.func.isRequired
};
export default connect(mapStateToProps, mapDispatchToProps)(EditImportListExclusionModalConnector);

@ -1,147 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
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 { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './EditImportListExclusionModalContent.css';
function EditImportListExclusionModalContent(props) {
const {
id,
isFetching,
error,
isSaving,
saveError,
item,
onInputChange,
onSavePress,
onModalClose,
onDeleteImportListExclusionPress,
...otherProps
} = props;
const {
movieTitle = '',
tmdbId,
movieYear
} = item;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{id ? translate('EditImportListExclusion') : translate('AddImportListExclusion')}
</ModalHeader>
<ModalBody className={styles.body}>
{
isFetching &&
<LoadingIndicator />
}
{
!isFetching && !!error &&
<Alert kind={kinds.DANGER}>
{translate('AddImportListExclusionError')}
</Alert>
}
{
!isFetching && !error &&
<Form
{...otherProps}
>
<FormGroup>
<FormLabel>{translate('TMDBId')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="tmdbId"
helpText={translate('TmdbIdHelpText')}
{...tmdbId}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('MovieTitle')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="movieTitle"
helpText={translate('MovieTitleHelpText')}
{...movieTitle}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('MovieYear')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="movieYear"
helpText={translate('MovieYearHelpText')}
{...movieYear}
onChange={onInputChange}
/>
</FormGroup>
</Form>
}
</ModalBody>
<ModalFooter>
{
id &&
<Button
className={styles.deleteButton}
kind={kinds.DANGER}
onPress={onDeleteImportListExclusionPress}
>
{translate('Delete')}
</Button>
}
<Button
onPress={onModalClose}
>
{translate('Cancel')}
</Button>
<SpinnerErrorButton
isSpinning={isSaving}
error={saveError}
onPress={onSavePress}
>
{translate('Save')}
</SpinnerErrorButton>
</ModalFooter>
</ModalContent>
);
}
EditImportListExclusionModalContent.propTypes = {
id: PropTypes.number,
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object,
item: PropTypes.object.isRequired,
onInputChange: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired,
onDeleteImportListExclusionPress: PropTypes.func
};
export default EditImportListExclusionModalContent;

@ -0,0 +1,201 @@
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 { PendingSection } from 'typings/pending';
import translate from 'Utilities/String/translate';
import styles from './EditImportListExclusionModalContent.css';
const newImportListExclusion = {
movieTitle: '',
movieYear: 0,
tmdbId: 0,
};
interface EditImportListExclusionModalContentProps {
id?: number;
onModalClose: () => void;
onDeleteImportListExclusionPress?: () => void;
}
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 {
id,
isFetching,
error,
isSaving,
saveError,
item: settings.settings as PendingSection<ImportListExclusion>,
...settings,
};
}
);
}
function EditImportListExclusionModalContent(
props: EditImportListExclusionModalContentProps
) {
const { id, onModalClose, onDeleteImportListExclusionPress } = props;
const dispatch = useDispatch();
const dispatchSetImportListExclusionValue = (payload: {
name: string;
value: string | number;
}) => {
// @ts-expect-error 'setImportListExclusionValue' isn't typed yet
dispatch(setImportListExclusionValue(payload));
};
const { isFetching, isSaving, item, error, saveError, ...otherProps } =
useSelector(createImportListExclusionSelector(props.id));
const previousIsSaving = usePrevious(isSaving);
const { movieTitle, movieYear, tmdbId } = item;
useEffect(() => {
if (!id) {
Object.keys(newImportListExclusion).forEach((name) => {
dispatchSetImportListExclusionValue({
name,
value:
newImportListExclusion[name as keyof typeof newImportListExclusion],
});
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
if (previousIsSaving && !isSaving && !saveError) {
onModalClose();
}
});
const onSavePress = useCallback(() => {
dispatch(saveImportListExclusion({ id }));
}, [dispatch, id]);
const onInputChange = useCallback(
(payload: { name: string; value: string | number }) => {
// @ts-expect-error 'setImportListExclusionValue' isn't typed yet
dispatch(setImportListExclusionValue(payload));
},
[dispatch]
);
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{id
? translate('EditImportListExclusion')
: translate('AddImportListExclusion')}
</ModalHeader>
<ModalBody className={styles.body}>
{isFetching && <LoadingIndicator />}
{!isFetching && !!error && (
<Alert kind={kinds.DANGER}>
{translate('AddImportListExclusionError')}
</Alert>
)}
{!isFetching && !error && (
<Form {...otherProps}>
<FormGroup>
<FormLabel>{translate('TMDBId')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="tmdbId"
helpText={translate('TmdbIdExcludeHelpText')}
{...tmdbId}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Title')}</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="movieTitle"
helpText={translate('MovieTitleToExcludeHelpText')}
{...movieTitle}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Year')}</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="movieYear"
helpText={translate('MovieYearToExcludeHelpText')}
{...movieYear}
onChange={onInputChange}
/>
</FormGroup>
</Form>
)}
</ModalBody>
<ModalFooter>
{id && (
<Button
className={styles.deleteButton}
kind={kinds.DANGER}
onPress={onDeleteImportListExclusionPress}
>
{translate('Delete')}
</Button>
)}
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
<SpinnerErrorButton
isSpinning={isSaving}
error={saveError}
onPress={onSavePress}
>
{translate('Save')}
</SpinnerErrorButton>
</ModalFooter>
</ModalContent>
);
}
export default EditImportListExclusionModalContent;

@ -1,118 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { saveImportListExclusion, setImportListExclusionValue } from 'Store/Actions/settingsActions';
import selectSettings from 'Store/Selectors/selectSettings';
import EditImportListExclusionModalContent from './EditImportListExclusionModalContent';
const newImportListExclusion = {
movieTitle: '',
tmdbId: 0,
movieYear: 0
};
function createImportListExclusionSelector() {
return createSelector(
(state, { id }) => id,
(state) => state.settings.importListExclusions,
(id, 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 {
id,
isFetching,
error,
isSaving,
saveError,
item: settings.settings,
...settings
};
}
);
}
function createMapStateToProps() {
return createSelector(
createImportListExclusionSelector(),
(importListExclusion) => {
return {
...importListExclusion
};
}
);
}
const mapDispatchToProps = {
setImportListExclusionValue,
saveImportListExclusion
};
class EditImportListExclusionModalContentConnector extends Component {
//
// Lifecycle
componentDidMount() {
if (!this.props.id) {
Object.keys(newImportListExclusion).forEach((name) => {
this.props.setImportListExclusionValue({
name,
value: newImportListExclusion[name]
});
});
}
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.isSaving && !this.props.isSaving && !this.props.saveError) {
this.props.onModalClose();
}
}
//
// Listeners
onInputChange = ({ name, value }) => {
this.props.setImportListExclusionValue({ name, value });
};
onSavePress = () => {
this.props.saveImportListExclusion({ id: this.props.id });
};
//
// Render
render() {
return (
<EditImportListExclusionModalContent
{...this.props}
onSavePress={this.onSavePress}
onInputChange={this.onInputChange}
/>
);
}
}
EditImportListExclusionModalContentConnector.propTypes = {
id: PropTypes.number,
isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object,
item: PropTypes.object.isRequired,
setImportListExclusionValue: PropTypes.func.isRequired,
saveImportListExclusion: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(EditImportListExclusionModalContentConnector);

@ -1,26 +0,0 @@
.importListExclusion {
display: flex;
align-items: stretch;
margin-bottom: 10px;
height: 30px;
border-bottom: 1px solid var(--borderColor);
line-height: 30px;
}
.movieTitle {
@add-mixin truncate;
flex: 0 1 600px;
}
.tmdbId,
.movieYear {
flex: 0 0 70px;
}
.actions {
display: flex;
justify-content: flex-end;
flex: 1 0 auto;
padding-right: 10px;
}

@ -1,115 +0,0 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditImportListExclusionModalConnector from './EditImportListExclusionModalConnector';
import styles from './ImportListExclusion.css';
class ImportListExclusion extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
isEditImportListExclusionModalOpen: false,
isDeleteImportListExclusionModalOpen: false
};
}
//
// Listeners
onEditImportListExclusionPress = () => {
this.setState({ isEditImportListExclusionModalOpen: true });
};
onEditImportListExclusionModalClose = () => {
this.setState({ isEditImportListExclusionModalOpen: false });
};
onDeleteImportListExclusionPress = () => {
this.setState({
isEditImportListExclusionModalOpen: false,
isDeleteImportListExclusionModalOpen: true
});
};
onDeleteImportListExclusionModalClose = () => {
this.setState({ isDeleteImportListExclusionModalOpen: false });
};
onConfirmDeleteImportListExclusion = () => {
this.props.onConfirmDeleteImportListExclusion(this.props.id);
};
//
// Render
render() {
const {
id,
movieTitle,
tmdbId,
movieYear
} = this.props;
return (
<div
className={classNames(
styles.importListExclusion
)}
>
<div className={styles.tmdbId}>{tmdbId}</div>
<div className={styles.movieTitle} title={movieTitle}>{movieTitle}</div>
<div className={styles.movieYear}>{movieYear}</div>
<div className={styles.actions}>
<Link
onPress={this.onEditImportListExclusionPress}
>
<Icon name={icons.EDIT} />
</Link>
</div>
<EditImportListExclusionModalConnector
id={id}
isOpen={this.state.isEditImportListExclusionModalOpen}
onModalClose={this.onEditImportListExclusionModalClose}
onDeleteImportListExclusionPress={this.onDeleteImportListExclusionPress}
/>
<ConfirmModal
isOpen={this.state.isDeleteImportListExclusionModalOpen}
kind={kinds.DANGER}
title={translate('DeleteImportListExclusion')}
message={translate('DeleteImportListExclusionMessageText')}
confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteImportListExclusion}
onCancel={this.onDeleteImportListExclusionModalClose}
/>
</div>
);
}
}
ImportListExclusion.propTypes = {
id: PropTypes.number.isRequired,
movieTitle: PropTypes.string.isRequired,
tmdbId: PropTypes.number.isRequired,
movieYear: PropTypes.number.isRequired,
onConfirmDeleteImportListExclusion: PropTypes.func.isRequired
};
ImportListExclusion.defaultProps = {
// The drag preview will not connect the drag handle.
connectDragSource: (node) => node
};
export default ImportListExclusion;

@ -0,0 +1,6 @@
.actions {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 35px;
white-space: nowrap;
}

@ -2,10 +2,6 @@
// Please do not change this file!
interface CssExports {
'actions': string;
'importListExclusion': string;
'movieTitle': string;
'movieYear': string;
'tmdbId': string;
}
export const cssExports: CssExports;
export default cssExports;

@ -0,0 +1,83 @@
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
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 styles from './ImportListExclusionRow.css';
interface ImportListExclusionRowProps extends ImportListExclusion {
isSelected: boolean;
onSelectedChange: (options: SelectStateInputProps) => void;
}
function ImportListExclusionRow(props: ImportListExclusionRowProps) {
const { id, tmdbId, movieTitle, movieYear, isSelected, onSelectedChange } =
props;
const dispatch = useDispatch();
const [
isEditImportListExclusionModalOpen,
setEditImportListExclusionModalOpen,
setEditImportListExclusionModalClosed,
] = useModalOpenState(false);
const [
isDeleteImportListExclusionModalOpen,
setDeleteImportListExclusionModalOpen,
setDeleteImportListExclusionModalClosed,
] = useModalOpenState(false);
const handleDeletePress = useCallback(() => {
dispatch(deleteImportListExclusion({ id }));
}, [id, dispatch]);
return (
<TableRow>
<TableSelectCell
id={id}
isSelected={isSelected}
onSelectedChange={onSelectedChange}
/>
<TableRowCell>{tmdbId}</TableRowCell>
<TableRowCell>{movieTitle}</TableRowCell>
<TableRowCell>{movieYear}</TableRowCell>
<TableRowCell className={styles.actions}>
<IconButton
name={icons.EDIT}
onPress={setEditImportListExclusionModalOpen}
/>
</TableRowCell>
<EditImportListExclusionModal
id={id}
isOpen={isEditImportListExclusionModalOpen}
onModalClose={setEditImportListExclusionModalClosed}
onDeleteImportListExclusionPress={setDeleteImportListExclusionModalOpen}
/>
<ConfirmModal
isOpen={isDeleteImportListExclusionModalOpen}
kind={kinds.DANGER}
title={translate('DeleteImportListExclusion')}
message={translate('DeleteImportListExclusionMessageText')}
confirmLabel={translate('Delete')}
onConfirm={handleDeletePress}
onCancel={setDeleteImportListExclusionModalClosed}
/>
</TableRow>
);
}
export default ImportListExclusionRow;

@ -1,24 +1,6 @@
.importListExclusionsHeader {
display: flex;
margin-bottom: 10px;
font-weight: bold;
}
.title {
flex: 0 1 600px;
}
.tmdbId,
.movieYear {
flex: 0 0 70px;
}
.addImportListExclusion {
display: flex;
justify-content: flex-end;
padding-right: 10px;
}
.actions {
composes: headerCell from '~Components/Table/TableHeaderCell.css';
.addButton {
text-align: center;
width: 35px;
white-space: nowrap;
}

@ -1,12 +1,7 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'addButton': string;
'addImportListExclusion': string;
'importListExclusionsHeader': string;
'movieYear': string;
'title': string;
'tmdbId': string;
'actions': string;
}
export const cssExports: CssExports;
export default cssExports;

@ -1,108 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import FieldSet from 'Components/FieldSet';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import PageSectionContent from 'Components/Page/PageSectionContent';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditImportListExclusionModalConnector from './EditImportListExclusionModalConnector';
import ImportListExclusion from './ImportListExclusion';
import styles from './ImportListExclusions.css';
class ImportListExclusions extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
isAddImportListExclusionModalOpen: false
};
}
//
// Listeners
onAddImportListExclusionPress = () => {
this.setState({ isAddImportListExclusionModalOpen: true });
};
onModalClose = () => {
this.setState({ isAddImportListExclusionModalOpen: false });
};
//
// Render
render() {
const {
items,
onConfirmDeleteImportListExclusion,
...otherProps
} = this.props;
return (
<FieldSet legend={translate('ImportListExclusions')}>
<PageSectionContent
errorMessage={translate('ImportListExclusionsLoadError')}
{...otherProps}
>
<div className={styles.importListExclusionsHeader}>
<div className={styles.tmdbId}>
{translate('TMDBId')}
</div>
<div className={styles.title}>
{translate('Title')}
</div>
<div className={styles.movieYear}>
{translate('Year')}
</div>
</div>
<div>
{
items.map((item, index) => {
return (
<ImportListExclusion
key={item.id}
{...item}
{...otherProps}
index={index}
onConfirmDeleteImportListExclusion={onConfirmDeleteImportListExclusion}
/>
);
})
}
</div>
<div className={styles.addImportListExclusion}>
<Link
className={styles.addButton}
onPress={this.onAddImportListExclusionPress}
>
<Icon name={icons.ADD} />
</Link>
</div>
<EditImportListExclusionModalConnector
isOpen={this.state.isAddImportListExclusionModalOpen}
onModalClose={this.onModalClose}
/>
</PageSectionContent>
</FieldSet>
);
}
}
ImportListExclusions.propTypes = {
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
onConfirmDeleteImportListExclusion: PropTypes.func.isRequired
};
export default ImportListExclusions;

@ -0,0 +1,309 @@
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 FieldSet from 'Components/FieldSet';
import IconButton from 'Components/Link/IconButton';
import SpinnerButton from 'Components/Link/SpinnerButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import PageSectionContent from 'Components/Page/PageSectionContent';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import Column from 'Components/Table/Column';
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 useSelectState from 'Helpers/Hooks/useSelectState';
import { icons, kinds } from 'Helpers/Props';
import {
bulkDeleteImportListExclusions,
clearImportListExclusions,
fetchImportListExclusions,
gotoImportListExclusionPage,
setImportListExclusionSort,
setImportListExclusionTableOption,
} from 'Store/Actions/Settings/importListExclusions';
import { CheckInputChanged } from 'typings/inputs';
import { SelectStateInputProps } from 'typings/props';
import { TableOptionsChangePayload } from 'typings/Table';
import {
registerPagePopulator,
unregisterPagePopulator,
} from 'Utilities/pagePopulator';
import translate from 'Utilities/String/translate';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
import EditImportListExclusionModal from './EditImportListExclusionModal';
import ImportListExclusionRow from './ImportListExclusionRow';
import styles from './ImportListExclusions.css';
const COLUMNS: Column[] = [
{
name: 'tmdbid',
label: () => translate('TMDBId'),
isVisible: true,
isSortable: true,
},
{
name: 'movieTitle',
label: () => translate('Title'),
isVisible: true,
isSortable: true,
},
{
name: 'movieYear',
label: () => translate('Year'),
isVisible: true,
isSortable: true,
},
{
className: styles.actions,
name: 'actions',
label: '',
isVisible: true,
isSortable: false,
},
];
function createImportListExclusionsSelector() {
return createSelector(
(state: AppState) => state.settings.importListExclusions,
(importListExclusions) => {
return {
...importListExclusions,
};
}
);
}
function ImportListExclusions() {
const requestCurrentPage = useCurrentPage();
const {
isFetching,
isPopulated,
items,
pageSize,
sortKey,
error,
sortDirection,
page,
totalPages,
totalRecords,
isDeleting,
deleteError,
} = useSelector(createImportListExclusionsSelector());
const dispatch = useDispatch();
const [isConfirmDeleteModalOpen, setIsConfirmDeleteModalOpen] =
useState(false);
const previousIsDeleting = usePrevious(isDeleting);
const [selectState, setSelectState] = useSelectState();
const { allSelected, allUnselected, selectedState } = selectState;
const selectedIds = useMemo(() => {
return getSelectedIds(selectedState);
}, [selectedState]);
const handleSelectAllChange = useCallback(
({ value }: CheckInputChanged) => {
setSelectState({ type: value ? 'selectAll' : 'unselectAll', items });
},
[items, setSelectState]
);
const handleSelectedChange = useCallback(
({ id, value, shiftKey = false }: SelectStateInputProps) => {
setSelectState({
type: 'toggleSelected',
items,
id,
isSelected: value,
shiftKey,
});
},
[items, setSelectState]
);
const handleDeleteSelectedPress = useCallback(() => {
setIsConfirmDeleteModalOpen(true);
}, [setIsConfirmDeleteModalOpen]);
const handleDeleteSelectedConfirmed = useCallback(() => {
dispatch(bulkDeleteImportListExclusions({ ids: selectedIds }));
setIsConfirmDeleteModalOpen(false);
}, [selectedIds, setIsConfirmDeleteModalOpen, dispatch]);
const handleConfirmDeleteModalClose = useCallback(() => {
setIsConfirmDeleteModalOpen(false);
}, [setIsConfirmDeleteModalOpen]);
const {
handleFirstPagePress,
handlePreviousPagePress,
handleNextPagePress,
handleLastPagePress,
handlePageSelect,
} = usePaging({
page,
totalPages,
gotoPage: gotoImportListExclusionPage,
});
const handleSortPress = useCallback(
(sortKey: { sortKey: string }) => {
dispatch(setImportListExclusionSort({ sortKey }));
},
[dispatch]
);
const handleTableOptionChange = useCallback(
(payload: TableOptionsChangePayload) => {
dispatch(setImportListExclusionTableOption(payload));
if (payload.pageSize) {
dispatch(gotoImportListExclusionPage({ page: 1 }));
}
},
[dispatch]
);
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) {
setSelectState({ type: 'unselectAll', items });
dispatch(fetchImportListExclusions());
}
}, [
previousIsDeleting,
isDeleting,
deleteError,
items,
dispatch,
setSelectState,
]);
const [
isAddImportListExclusionModalOpen,
setAddImportListExclusionModalOpen,
setAddImportListExclusionModalClosed,
] = useModalOpenState(false);
const isFetchingForFirstTime = isFetching && !isPopulated;
return (
<FieldSet legend={translate('ImportListExclusions')}>
<PageSectionContent
errorMessage={translate('ImportListExclusionsLoadError')}
isFetching={isFetchingForFirstTime}
isPopulated={isPopulated}
error={error}
>
<Table
selectAll={true}
allSelected={allSelected}
allUnselected={allUnselected}
columns={COLUMNS}
canModifyColumns={false}
pageSize={pageSize}
sortKey={sortKey}
sortDirection={sortDirection}
onTableOptionChange={handleTableOptionChange}
onSelectAllChange={handleSelectAllChange}
onSortPress={handleSortPress}
>
<TableBody>
{items.map((item) => {
return (
<ImportListExclusionRow
key={item.id}
{...item}
isSelected={selectedState[item.id] || false}
onSelectedChange={handleSelectedChange}
/>
);
})}
<TableRow>
<TableRowCell colSpan={4}>
<SpinnerButton
kind={kinds.DANGER}
isSpinning={isDeleting}
isDisabled={!selectedIds.length}
onPress={handleDeleteSelectedPress}
>
{translate('Delete')}
</SpinnerButton>
</TableRowCell>
<TableRowCell>
<IconButton
name={icons.ADD}
onPress={setAddImportListExclusionModalOpen}
/>
</TableRowCell>
</TableRow>
</TableBody>
</Table>
<TablePager
page={page}
totalPages={totalPages}
totalRecords={totalRecords}
isFetching={isFetching}
onFirstPagePress={handleFirstPagePress}
onPreviousPagePress={handlePreviousPagePress}
onNextPagePress={handleNextPagePress}
onLastPagePress={handleLastPagePress}
onPageSelect={handlePageSelect}
/>
<EditImportListExclusionModal
isOpen={isAddImportListExclusionModalOpen}
onModalClose={setAddImportListExclusionModalClosed}
/>
<ConfirmModal
isOpen={isConfirmDeleteModalOpen}
kind={kinds.DANGER}
title={translate('DeleteSelected')}
message={translate('DeleteSelectedImportListExclusionsMessageText')}
confirmLabel={translate('DeleteSelected')}
onConfirm={handleDeleteSelectedConfirmed}
onCancel={handleConfirmDeleteModalClose}
/>
</PageSectionContent>
</FieldSet>
);
}
export default ImportListExclusions;

@ -1,59 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { deleteImportListExclusion, fetchImportListExclusions } from 'Store/Actions/settingsActions';
import ImportListExclusions from './ImportListExclusions';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.importListExclusions,
(importListExclusions) => {
return {
...importListExclusions
};
}
);
}
const mapDispatchToProps = {
fetchImportListExclusions,
deleteImportListExclusion
};
class ImportListExclusionsConnector extends Component {
//
// Lifecycle
componentDidMount() {
this.props.fetchImportListExclusions();
}
//
// Listeners
onConfirmDeleteImportListExclusion = (id) => {
this.props.deleteImportListExclusion({ id });
};
//
// Render
render() {
return (
<ImportListExclusions
{...this.state}
{...this.props}
onConfirmDeleteImportListExclusion={this.onConfirmDeleteImportListExclusion}
/>
);
}
}
ImportListExclusionsConnector.propTypes = {
fetchImportListExclusions: PropTypes.func.isRequired,
deleteImportListExclusion: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(ImportListExclusionsConnector);

@ -7,7 +7,7 @@ import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import { icons } from 'Helpers/Props';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import translate from 'Utilities/String/translate';
import ImportListExclusionsConnector from './ImportListExclusions/ImportListExclusionsConnector';
import ImportListExclusions from './ImportListExclusions/ImportListExclusions';
import ImportListsConnector from './ImportLists/ImportListsConnector';
import ManageImportListsModal from './ImportLists/Manage/ManageImportListsModal';
import ImportListOptions from './Options/ImportListOptions';
@ -103,7 +103,7 @@ class ImportListSettings extends Component {
onChildStateChange={this.onChildStateChange}
/>
<ImportListExclusionsConnector />
<ImportListExclusions />
<ManageImportListsModal
isOpen={isManageImportListsOpen}

@ -1,9 +1,13 @@
import { createAction } from 'redux-actions';
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
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 { createThunk } from 'Store/thunks';
import createSetTableOptionReducer from 'Store/Actions/Creators/Reducers/createSetTableOptionReducer';
import { createThunk, handleThunks } from 'Store/thunks';
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
//
// Variables
@ -14,18 +18,28 @@ 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,
@ -45,27 +59,52 @@ export default {
isFetching: false,
isPopulated: false,
error: null,
pageSize: 20,
items: [],
isSaving: false,
saveError: null,
isDeleting: false,
deleteError: null,
pendingChanges: {}
},
//
// Action Handlers
actionHandlers: {
[FETCH_IMPORT_LIST_EXCLUSIONS]: createFetchHandler(section, '/exclusions'),
actionHandlers: handleThunks({
...createServerSideCollectionHandlers(
section,
'/exclusions/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, '/exclusions'),
[DELETE_IMPORT_LIST_EXCLUSION]: createRemoveItemHandler(section, '/exclusions')
},
[DELETE_IMPORT_LIST_EXCLUSION]: createRemoveItemHandler(section, '/exclusions'),
[BULK_DELETE_IMPORT_LIST_EXCLUSIONS]: createBulkRemoveItemHandler(section, '/exclusions/bulk')
}),
//
// Reducers
reducers: {
[SET_IMPORT_LIST_EXCLUSION_VALUE]: createSetSettingValueReducer(section)
[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
})
}
};

@ -94,7 +94,8 @@ export const defaultState = {
};
export const persistState = [
'settings.advancedSettings'
'settings.advancedSettings',
'settings.importListExclusions.pageSize'
];
//

@ -1,45 +1,44 @@
import { createSelector } from 'reselect';
import AppSectionState, {
AppSectionItemState,
} from 'App/State/AppSectionState';
import { AppSectionItemState } from 'App/State/AppSectionState';
import AppState from 'App/State/AppState';
import SettingsAppState from 'App/State/SettingsAppState';
import selectSettings from 'Store/Selectors/selectSettings';
import { PendingSection } from 'typings/pending';
type SettingNames = keyof Omit<AppState['settings'], 'advancedSettings'>;
type GetSectionState<Name extends SettingNames> = AppState['settings'][Name];
type GetSettingsSectionItemType<Name extends SettingNames> =
GetSectionState<Name> extends AppSectionItemState<infer R>
? R
: GetSectionState<Name> extends AppSectionState<infer R>
? R
type SectionsWithItemNames = {
[K in keyof SettingsAppState]: SettingsAppState[K] extends AppSectionItemState<unknown>
? K
: never;
}[keyof SettingsAppState];
type AppStateWithPending<Name extends SettingNames> = {
item?: GetSettingsSectionItemType<Name>;
pendingChanges?: Partial<GetSettingsSectionItemType<Name>>;
saveError?: Error;
} & GetSectionState<Name>;
type GetSectionState<Name extends SectionsWithItemNames> =
SettingsAppState[Name];
type GetSettingsSectionItemType<Name extends SectionsWithItemNames> =
GetSectionState<Name> extends AppSectionItemState<infer R> ? R : never;
function createSettingsSectionSelector<Name extends SettingNames>(
section: Name
) {
function createSettingsSectionSelector<
Name extends SectionsWithItemNames,
T extends GetSettingsSectionItemType<Name>
>(section: Name) {
return createSelector(
(state: AppState) => state.settings[section],
(sectionSettings) => {
const { item, pendingChanges, saveError, ...other } =
sectionSettings as AppStateWithPending<Name>;
const { item, pendingChanges, ...other } = sectionSettings;
const { settings, ...rest } = selectSettings(
item,
pendingChanges,
saveError
);
const saveError =
'saveError' in sectionSettings ? sectionSettings.saveError : undefined;
const {
settings,
pendingChanges: selectedPendingChanges,
...rest
} = selectSettings(item, pendingChanges, saveError);
return {
...other,
saveError,
settings: settings as PendingSection<GetSettingsSectionItemType<Name>>,
settings: settings as PendingSection<T>,
pendingChanges: selectedPendingChanges as Partial<T>,
...rest,
};
}

@ -0,0 +1,7 @@
import ModelBase from 'App/ModelBase';
export default interface ImportListExclusion extends ModelBase {
tmdbId: number;
movieTitle: string;
movieYear: number;
}

@ -0,0 +1,6 @@
import Column from 'Components/Table/Column';
export interface TableOptionsChangePayload {
pageSize?: number;
columns: Column[];
}

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Movies.Events;
@ -11,6 +12,7 @@ namespace NzbDrone.Core.ImportLists.ImportExclusions
ImportListExclusion Add(ImportListExclusion importListExclusion);
List<ImportListExclusion> Add(List<ImportListExclusion> importListExclusions);
List<ImportListExclusion> All();
PagingSpec<ImportListExclusion> Paged(PagingSpec<ImportListExclusion> pagingSpec);
bool IsMovieExcluded(int tmdbId);
void Delete(int id);
void Delete(List<int> ids);
@ -78,6 +80,11 @@ namespace NzbDrone.Core.ImportLists.ImportExclusions
return _repo.All().ToList();
}
public PagingSpec<ImportListExclusion> Paged(PagingSpec<ImportListExclusion> pagingSpec)
{
return _repo.GetPaged(pagingSpec);
}
public List<int> AllExcludedTmdbIds()
{
return _repo.AllExcludedTmdbIds();

@ -111,7 +111,7 @@
"TorrentDelay": "تأخير السيل",
"Tomorrow": "غدا",
"Today": "اليوم",
"TmdbIdHelpText": "معرّف TMDb الخاص بالفيلم المراد استبعاده",
"TmdbIdExcludeHelpText": "معرّف TMDb الخاص بالفيلم المراد استبعاده",
"TMDBId": "معرف TMDb",
"TMDb": "TMDb",
"Titles": "الألقاب",
@ -276,9 +276,9 @@
"MustNotContain": "يجب ألا يحتوي",
"MustContain": "يجب أن يحتوي على",
"MultiLanguage": "متعدد اللغات",
"MovieYearHelpText": "عام الفيلم المطلوب استبعاده",
"MovieYearToExcludeHelpText": "عام الفيلم المطلوب استبعاده",
"MovieYear": "سنة الفيلم",
"MovieTitleHelpText": "عنوان الفيلم المراد استبعاده (يمكن أن يكون أي شيء ذي معنى)",
"MovieTitleToExcludeHelpText": "عنوان الفيلم المراد استبعاده (يمكن أن يكون أي شيء ذي معنى)",
"MovieTitle": "عنوان الفيلم",
"MoviesSelectedInterp": "تم تحديد {0} فيلم (أفلام)",
"Movies": "أفلام",

@ -572,7 +572,7 @@
"Title": "Заглавие",
"Titles": "Заглавия",
"TMDBId": "Идентификатор на TMDb",
"TmdbIdHelpText": "Идентификаторът на TMDb на филма за изключване",
"TmdbIdExcludeHelpText": "Идентификаторът на TMDb на филма за изключване",
"Today": "Днес",
"TorrentDelay": "Торент закъснение",
"TorrentDelayHelpText": "Забавете за минути, за да изчакате, преди да вземете порой",
@ -883,9 +883,9 @@
"Movies": "Филми",
"MoviesSelectedInterp": "{0} Избран / и филм / и",
"MovieTitle": "Заглавие на филма",
"MovieTitleHelpText": "Заглавието на филма, който трябва да се изключи (може да бъде всичко смислено)",
"MovieTitleToExcludeHelpText": "Заглавието на филма, който трябва да се изключи (може да бъде всичко смислено)",
"MovieYear": "Филмова година",
"MovieYearHelpText": "Годината на филма за изключване",
"MovieYearToExcludeHelpText": "Годината на филма за изключване",
"MustContain": "Трябва да съдържа",
"MustNotContain": "Не трябва да съдържа",
"NamingSettings": "Настройки за именуване",

@ -99,9 +99,9 @@
"MovieFolderFormat": "Format de carpeta de pel·lícules",
"MovieIndex": "Índex de pel·lícules",
"MovieTitle": "Títol de la pel·lícula",
"MovieTitleHelpText": "El títol de la pel·lícula a excloure (pot ser qualsevol cosa amb sentit)",
"MovieTitleToExcludeHelpText": "El títol de la pel·lícula a excloure (pot ser qualsevol cosa amb sentit)",
"MovieYear": "Any de la pel·lícula",
"MovieYearHelpText": "L'any de la pel·lícula a excloure",
"MovieYearToExcludeHelpText": "L'any de la pel·lícula a excloure",
"NoAltTitle": "No hi ha títols alternatius.",
"NoBackupsAreAvailable": "No hi ha còpies de seguretat disponibles",
"NoChange": "Cap canvi",
@ -869,7 +869,7 @@
"Titles": "Títols",
"TMDb": "TMDb",
"TMDBId": "Identificador de TMDb",
"TmdbIdHelpText": "L'identificador de TMDb de la pel·lícula a excloure",
"TmdbIdExcludeHelpText": "L'identificador de TMDb de la pel·lícula a excloure",
"TmdbVotes": "Vots de TMDb",
"Today": "Avui",
"Tomorrow": "Demà",

@ -376,9 +376,9 @@
"Movies": "Filmy",
"MoviesSelectedInterp": "{0} Vybrané filmy",
"MovieTitle": "Název filmu",
"MovieTitleHelpText": "Název filmu, který se má vyloučit (může mít cokoli smysluplného)",
"MovieTitleToExcludeHelpText": "Název filmu, který se má vyloučit (může mít cokoli smysluplného)",
"MovieYear": "Filmový rok",
"MovieYearHelpText": "Rok filmu, který se má vyloučit",
"MovieYearToExcludeHelpText": "Rok filmu, který se má vyloučit",
"MustContain": "Musí obsahovat",
"MustNotContain": "Nesmí obsahovat",
"NamingSettings": "Nastavení pojmenování",
@ -865,7 +865,7 @@
"Title": "Titul",
"Titles": "Tituly",
"TMDBId": "ID TMDb",
"TmdbIdHelpText": "ID TMDb filmu, které se má vyloučit",
"TmdbIdExcludeHelpText": "ID TMDb filmu, které se má vyloučit",
"Today": "Dnes",
"TorrentDelay": "Torrent Delay",
"TorrentDelayHelpText": "Zpoždění v minutách čekání před popadnutím torrentu",

@ -430,9 +430,9 @@
"MovieTitle": "Filmtitel",
"EditDelayProfile": "Rediger forsinkelsesprofil",
"Name": "Navn",
"MovieTitleHelpText": "Titlen på den film, der skal ekskluderes (kan være noget meningsfuldt)",
"MovieTitleToExcludeHelpText": "Titlen på den film, der skal ekskluderes (kan være noget meningsfuldt)",
"MovieYear": "Filmår",
"MovieYearHelpText": "Året for filmen at udelukke",
"MovieYearToExcludeHelpText": "Året for filmen at udelukke",
"MustContain": "Skal indeholde",
"MustNotContain": "Må ikke indeholde",
"NamingSettings": "Navngivningsindstillinger",
@ -845,7 +845,7 @@
"Title": "Titel",
"Titles": "Titler",
"TMDBId": "TMDb Id",
"TmdbIdHelpText": "TMDb-id for filmen, der skal ekskluderes",
"TmdbIdExcludeHelpText": "TMDb-id for filmen, der skal ekskluderes",
"Today": "I dag",
"TorrentDelay": "Torrentforsinkelse",
"TorrentDelayHelpText": "Forsink i minutter, før du tager fat i en torrent",

@ -413,9 +413,9 @@
"MovieID": "Film ID",
"MovieInfoLanguageHelpTextWarning": "Seite muss neugeladen werden",
"MovieIsDownloading": "Film ist am herunterladen",
"MovieTitleHelpText": "Der Titel des Filmes der augeschlossen werden soll",
"MovieTitleToExcludeHelpText": "Der Titel des Filmes der augeschlossen werden soll",
"MovieYear": "Erscheinungsjahr",
"MovieYearHelpText": "Das Erscheinungsjahr des Filmes der ausgeschlossen werden soll",
"MovieYearToExcludeHelpText": "Das Erscheinungsjahr des Filmes der ausgeschlossen werden soll",
"MustContain": "Muss beinhalten",
"MustNotContain": "Darf nicht beinhalten",
"NamingSettings": "Bennenungs Einstellungen",
@ -503,7 +503,7 @@
"Uptime": "Betriebszeit",
"UrlBase": "URL-Basis",
"TMDBId": "TMDb ID",
"TmdbIdHelpText": "Die TMDb ID für den Ausschluss",
"TmdbIdExcludeHelpText": "Die TMDb ID für den Ausschluss",
"TorrentDelay": "Torrent-Verzögerung",
"TorrentDelayHelpText": "Verzögerung in Minuten, bevor ein Torrent herunterladen wird",
"Torrents": "Torrents",

@ -372,9 +372,9 @@
"MoviesSelectedInterp": "{0} Επιλεγμένες ταινίες",
"MovieTitle": "Τίτλος ταινίας",
"Level": "Επίπεδο",
"MovieTitleHelpText": "Ο τίτλος της ταινίας προς εξαίρεση (μπορεί να είναι οτιδήποτε νόημα)",
"MovieTitleToExcludeHelpText": "Ο τίτλος της ταινίας προς εξαίρεση (μπορεί να είναι οτιδήποτε νόημα)",
"MovieYear": "Έτος ταινίας",
"MovieYearHelpText": "Το έτος της ταινίας που αποκλείεται",
"MovieYearToExcludeHelpText": "Το έτος της ταινίας που αποκλείεται",
"MustContain": "Πρέπει να περιέχει",
"MustNotContain": "Δεν πρέπει να περιέχει",
"NamingSettings": "Ρυθμίσεις ονομάτων",
@ -846,7 +846,7 @@
"Time": "χρόνος",
"Title": "Τίτλος",
"Titles": "Τίτλοι",
"TmdbIdHelpText": "Το αναγνωριστικό TMDb της ταινίας για εξαίρεση",
"TmdbIdExcludeHelpText": "Το αναγνωριστικό TMDb της ταινίας για εξαίρεση",
"Today": "Σήμερα",
"TorrentDelay": "Καθυστέρηση Torrent",
"TorrentDelayHelpText": "Καθυστέρηση σε λίγα λεπτά για να περιμένετε πριν πιάσετε ένα χείμαρρο",

@ -336,8 +336,10 @@
"DeleteRestrictionHelpText": "Are you sure you want to delete this restriction?",
"DeleteRootFolder": "Delete Root Folder",
"DeleteRootFolderMessageText": "Are you sure you want to delete the root folder '{path}'?",
"DeleteSelected": "Delete Selected",
"DeleteSelectedDownloadClients": "Delete Download Client(s)",
"DeleteSelectedDownloadClientsMessageText": "Are you sure you want to delete {count} selected download client(s)?",
"DeleteSelectedImportListExclusionsMessageText": "Are you sure you want to delete the selected import list exclusions?",
"DeleteSelectedImportLists": "Delete Import List(s)",
"DeleteSelectedImportListsMessageText": "Are you sure you want to delete {count} selected import list(s)?",
"DeleteSelectedIndexers": "Delete Indexer(s)",
@ -975,9 +977,9 @@
"MovieOnly": "Movie Only",
"MovieSearchResultsLoadError": "Unable to load results for this movie search. Try again later",
"MovieTitle": "Movie Title",
"MovieTitleHelpText": "The title of the movie to exclude (can be anything meaningful)",
"MovieTitleToExcludeHelpText": "The title of the movie to exclude (can be anything meaningful)",
"MovieYear": "Movie Year",
"MovieYearHelpText": "The year of the movie to exclude",
"MovieYearToExcludeHelpText": "The year of the movie to exclude",
"Movies": "Movies",
"MoviesSelectedInterp": "{count} Movie(s) Selected",
"MultiLanguage": "Multi-Language",
@ -1674,7 +1676,7 @@
"Timeleft": "Time Left",
"Title": "Title",
"Titles": "Titles",
"TmdbIdHelpText": "The TMDb Id of the movie to exclude",
"TmdbIdExcludeHelpText": "The TMDb Id of the movie to exclude",
"TmdbRating": "TMDb Rating",
"TmdbVotes": "TMDb Votes",
"Today": "Today",

@ -408,7 +408,7 @@
"Torrents": "Torrents",
"TorrentDelayHelpText": "Retraso en minutos a esperar antes de capturar un torrent",
"TorrentDelay": "Retraso de torrent",
"TmdbIdHelpText": "La Id de TMDb de la película a exluir",
"TmdbIdExcludeHelpText": "La Id de TMDb de la película a exluir",
"TMDBId": "TMDb Id",
"TestAllLists": "Probar todas las listas",
"TestAllIndexers": "Probar todos los indexadores",
@ -507,9 +507,9 @@
"NamingSettings": "Opciones de nombrado",
"MustNotContain": "No debe contener",
"MustContain": "Debe contener",
"MovieYearHelpText": "Año de la película a excluir",
"MovieYearToExcludeHelpText": "Año de la película a excluir",
"MovieYear": "Año de la Película",
"MovieTitleHelpText": "Ttítulo de la película a excluir (puedes ser cualquier cosa)",
"MovieTitleToExcludeHelpText": "Ttítulo de la película a excluir (puedes ser cualquier cosa)",
"MovieIsDownloading": "Le película está descargando",
"MovieInfoLanguageHelpTextWarning": "Recargar el Navegador",
"MovieID": "ID de Película",

@ -320,7 +320,7 @@
"RelativePath": "Suhteellinen sijainti",
"ReleaseRejected": "Vapautus hylätty",
"MovieTitle": "Elokuvan nimi",
"MovieTitleHelpText": "Poissuljettava elokuvan nimi (voi olla mikä tahansa mielekäs)",
"MovieTitleToExcludeHelpText": "Poissuljettava elokuvan nimi (voi olla mikä tahansa mielekäs)",
"MovieYear": "Elokuvan vuosi",
"TestAllClients": "Lataustyökalujen testaus",
"TestAllIndexers": "Tietolähteiden testaus",
@ -328,7 +328,7 @@
"AddRemotePathMapping": "Lisää etäsijainnin kohdistus",
"Apply": "Käytä",
"Analytics": "Analytiikka",
"MovieYearHelpText": "Elokuvan vuosi, joka suljetaan pois",
"MovieYearToExcludeHelpText": "Elokuvan vuosi, joka suljetaan pois",
"Date": "Päiväys",
"MustContain": "Täytyy sisältää",
"MustNotContain": "Ei voi sisältää",
@ -849,7 +849,7 @@
"Title": "Nimike",
"Titles": "Nimikkeet",
"TMDBId": "TMDB ID",
"TmdbIdHelpText": "Ohitettavan elokuvan TMDB ID.",
"TmdbIdExcludeHelpText": "Ohitettavan elokuvan TMDB ID.",
"Today": "Tänään",
"TorrentDelay": "Torrent-viive",
"TorrentDelayHelpText": "Minuuttiviive, joka odotetaan ennen julkaisun Torrent-kaappausta.",

@ -590,7 +590,7 @@
"UiLanguage": "Langue de l'IU",
"TotalFileSize": "Taille totale des fichiers",
"Torrents": "Torrents",
"TmdbIdHelpText": "L'ID TMDb du film à exclure",
"TmdbIdExcludeHelpText": "L'ID TMDb du film à exclure",
"TMDBId": "Identifiant TMDb",
"TestAllLists": "Tester toutes les listes",
"TestAllIndexers": "Testez tous les indexeurs",
@ -718,10 +718,10 @@
"NetCore": ".NET",
"NamingSettings": "Paramètres de dénomination",
"MustNotContain": "Ne doit pas contenir",
"MovieYearHelpText": "L'année de film à exclure",
"MovieYearToExcludeHelpText": "L'année de film à exclure",
"MustContain": "Doit contenir",
"MovieYear": "Année du film",
"MovieTitleHelpText": "Le titre du film à exclure (peut être quelque chose de significatif)",
"MovieTitleToExcludeHelpText": "Le titre du film à exclure (peut être quelque chose de significatif)",
"MovieIndexScrollTop": "Index des films : faire défiler vers le haut",
"MovieIndexScrollBottom": "Index des films : faire défiler vers le bas",
"MovieDetailsPreviousMovie": "Détails du film : Film Précédent",

@ -9,7 +9,7 @@
"AvailabilityDelayHelpText": "משך הזמן לפני או אחרי התאריך הזמין לחיפוש הסרט",
"BackupNow": "גיבוי עכשיו",
"ChownGroupHelpText": "שם הקבוצה או ה- gid. השתמש ב- gid עבור מערכות קבצים מרוחקות.",
"MovieYearHelpText": "שנת הסרט לא הכללה",
"MovieYearToExcludeHelpText": "שנת הסרט לא הכללה",
"ImportErrors": "ייבוא שגיאות",
"IndexersSettingsSummary": "אינדקסים ומגבלות שחרור",
"ReplaceWithDash": "החלף ב- Dash",
@ -84,7 +84,7 @@
"WhitelistedSubtitleTags": "תגיות כתוביות ברשימת ההיתרים",
"MissingMonitoredAndConsideredAvailable": "חסר (מנוטר)",
"ListTagsHelpText": "פריטי רשימת תגים יתווספו עם",
"MovieTitleHelpText": "כותרת הסרט לאי הכללה (יכולה להיות כל דבר בעל משמעות)",
"MovieTitleToExcludeHelpText": "כותרת הסרט לאי הכללה (יכולה להיות כל דבר בעל משמעות)",
"MovieYear": "שנת הסרט",
"DownloadPropersAndRepacksHelpTextCustomFormat": "השתמש ב'אל תעדיף 'כדי למיין לפי ציון פורמט מותאם אישית על פני Propers / Repacks",
"AddListExclusionMovieHelpText": "מנע מלהוסיף סרט לרדאר על ידי רשימות",
@ -850,7 +850,7 @@
"Title": "כותרת",
"Titles": "כותרות",
"TMDBId": "מזהה TMDb",
"TmdbIdHelpText": "מזהה ה- TMDb של הסרט לא לכלול",
"TmdbIdExcludeHelpText": "מזהה ה- TMDb של הסרט לא לכלול",
"Today": "היום",
"TorrentDelay": "עיכוב סיקור",
"TorrentDelayHelpText": "התעכב תוך דקות להמתין לפני שתופס סיקור",

@ -516,9 +516,9 @@
"Movies": "चलचित्र",
"MoviesSelectedInterp": "{0} मूवी (s) चयनित",
"MovieTitle": "चलचित्र शीर्षक",
"MovieTitleHelpText": "बहिष्कृत करने के लिए फिल्म का शीर्षक (सार्थक कुछ भी हो सकता है)",
"MovieTitleToExcludeHelpText": "बहिष्कृत करने के लिए फिल्म का शीर्षक (सार्थक कुछ भी हो सकता है)",
"MovieYear": "मूवी वर्ष",
"MovieYearHelpText": "फिल्म को बाहर करने का वर्ष",
"MovieYearToExcludeHelpText": "फिल्म को बाहर करने का वर्ष",
"MustContain": "शामिल होना चाहिए",
"MustNotContain": "कंटेनर नहीं होना चाहिए",
"NamingSettings": "नामकरण सेटिंग्स",
@ -888,7 +888,7 @@
"ThisCannotBeCancelled": "यह एक बार रद्द नहीं किया जा सकता है जब तक कि रेडर को फिर से शुरू किए बिना।",
"Time": "समय",
"TMDBId": "TMDb Id",
"TmdbIdHelpText": "बाहर करने के लिए फिल्म की TMDb Id",
"TmdbIdExcludeHelpText": "बाहर करने के लिए फिल्म की TMDb Id",
"Today": "आज",
"TorrentDelayTime": "धार विलंब: {0}",
"Torrents": "टोरेंट",

@ -482,9 +482,9 @@
"Name": "Név",
"MustNotContain": "Nem tartalmazhat",
"MustContain": "Tartalmaznia kell",
"MovieYearHelpText": "A kizárandó film(ek) éve",
"MovieYearToExcludeHelpText": "A kizárandó film(ek) éve",
"MovieYear": "Kiadási év",
"MovieTitleHelpText": "A kizárandó film címe (bármi értelmes lehet)",
"MovieTitleToExcludeHelpText": "A kizárandó film címe (bármi értelmes lehet)",
"MovieTitle": "Filmcím",
"MoviesSelectedInterp": "{0} Kiválasztott film(ek)",
"Movies": "Filmek",
@ -619,7 +619,7 @@
"Torrents": "Torrentek",
"TorrentDelayHelpText": "Percek késése, hogy várjon, mielőtt megragad egy torrentet",
"TorrentDelay": "Torrent Késleltetés",
"TmdbIdHelpText": "A kizárandó film TMDb azonosítója",
"TmdbIdExcludeHelpText": "A kizárandó film TMDb azonosítója",
"TMDBId": "TMDb azonosító",
"Titles": "Címek",
"Title": "Cím",

@ -342,7 +342,7 @@
"Calendar": "Dagatal",
"MoviesSelectedInterp": "{0} Kvikmynd (ir) valdar",
"MovieTitle": "Kvikmyndatitill",
"MovieTitleHelpText": "Titill myndarinnar til að útiloka (getur verið hvað sem er þýðingarmikill)",
"MovieTitleToExcludeHelpText": "Titill myndarinnar til að útiloka (getur verið hvað sem er þýðingarmikill)",
"ClientPriority": "Forgangur viðskiptavinar",
"DeleteCustomFormat": "Eyða sérsniðnu sniði",
"DockerUpdater": "uppfærðu bryggjugáminn til að fá uppfærsluna",
@ -358,7 +358,7 @@
"InvalidFormat": "Ógilt snið",
"Medium": "Miðlungs",
"Minutes": "Fundargerð",
"MovieYearHelpText": "Ár kvikmyndarinnar til að útiloka",
"MovieYearToExcludeHelpText": "Ár kvikmyndarinnar til að útiloka",
"Profiles": "Snið",
"Reason": "Ástæða",
"Released": "Sleppt",
@ -859,7 +859,7 @@
"Title": "Titill",
"Titles": "Titlar",
"TMDBId": "TMDb kt",
"TmdbIdHelpText": "TMDb auðkenni myndarinnar til að útiloka",
"TmdbIdExcludeHelpText": "TMDb auðkenni myndarinnar til að útiloka",
"Today": "Í dag",
"TorrentDelay": "Torrent Delay",
"TorrentDelayHelpText": "Seinkaðu í nokkrar mínútur til að bíða áður en þú grípur strauminn",

@ -466,9 +466,9 @@
"NamingSettings": "Impostazioni di denominazione",
"MustNotContain": "Non Deve Contenere",
"MustContain": "Deve Contenere",
"MovieYearHelpText": "Anno del film da escludere",
"MovieYearToExcludeHelpText": "Anno del film da escludere",
"MovieYear": "Anno del film",
"MovieTitleHelpText": "Titolo del Film da escludere (può essere qualunque cosa significativa)",
"MovieTitleToExcludeHelpText": "Titolo del Film da escludere (può essere qualunque cosa significativa)",
"MoviesSelectedInterp": "{count} Film selezionato(i)",
"MovieIsUnmonitored": "Il film è non monitorato",
"MovieIsOnImportExclusionList": "Il Film è nella lista di esclusione dell'importazione",
@ -654,7 +654,7 @@
"Torrents": "Torrents",
"TorrentDelayHelpText": "Ritardo in minuti da aspettare prima di prendere un torrent",
"TorrentDelay": "Ritardo del torrent",
"TmdbIdHelpText": "Id di TMDb del film da escludere",
"TmdbIdExcludeHelpText": "Id di TMDb del film da escludere",
"TMDBId": "ID di TMDb",
"TimeFormat": "Formato Orario",
"TestAllLists": "Testa tutte le liste",

@ -89,7 +89,7 @@
"AuthenticationMethodHelpText": "{appName}にアクセスするにはユーザー名とパスワードが必要です",
"Component": "成分",
"Proxy": "プロキシ",
"MovieTitleHelpText": "除外する映画のタイトル(意味のあるものであれば何でもかまいません)",
"MovieTitleToExcludeHelpText": "除外する映画のタイトル(意味のあるものであれば何でもかまいません)",
"TestAllIndexers": "すべてのインデクサーをテストする",
"AnalyseVideoFiles": "ビデオファイルを分析する",
"AppDataDirectory": "AppDataディレクトリ",
@ -333,7 +333,7 @@
"Actions": "行動",
"AddMovies": "映画を追加する",
"IndexersSettingsSummary": "インデクサーとリリース制限",
"MovieYearHelpText": "除外する映画の年",
"MovieYearToExcludeHelpText": "除外する映画の年",
"Age": "年齢",
"NetCore": ".NET Core",
"NoBackupsAreAvailable": "バックアップは利用できません",
@ -847,7 +847,7 @@
"Title": "題名",
"Titles": "タイトル",
"TMDBId": "TMDbID",
"TmdbIdHelpText": "除外する映画のTMDbID",
"TmdbIdExcludeHelpText": "除外する映画のTMDbID",
"Today": "今日",
"TorrentDelay": "トレント遅延",
"Trace": "痕跡",

@ -328,7 +328,7 @@
"Movies": "영화",
"MoviesSelectedInterp": "선택한 영화 {0} 개",
"MovieTitle": "영화 제목",
"MovieTitleHelpText": "제외 할 영화 제목 (의미있는 것은 무엇이든 가능)",
"MovieTitleToExcludeHelpText": "제외 할 영화 제목 (의미있는 것은 무엇이든 가능)",
"Actions": "동작",
"Added": "추가됨",
"AddNew": "새로 추가하기",
@ -337,7 +337,7 @@
"Agenda": "일정",
"Failed": "실패한",
"MovieYear": "영화 연도",
"MovieYearHelpText": "제외 할 영화 연도",
"MovieYearToExcludeHelpText": "제외 할 영화 연도",
"MustContain": "포함해야 함",
"MustNotContain": "포함해서는 안 됨",
"NamingSettings": "이름 지정 설정",
@ -854,7 +854,7 @@
"Title": "제목",
"Titles": "제목",
"TMDBId": "TMDb ID",
"TmdbIdHelpText": "제외 할 영화의 TMDb ID",
"TmdbIdExcludeHelpText": "제외 할 영화의 TMDb ID",
"Today": "오늘",
"TorrentDelay": "급류 지연",
"TorrentDelayHelpText": "급류를 잡기 전에 대기하는 데 몇 분이 걸립니다.",

@ -483,8 +483,8 @@
"MinimumCustomFormatScoreHelpText": "Minimum eigen formaat score toegelaten om te downloaden",
"MinimumFreeSpaceHelpText": "Voorkom importeren indien de resulterende schijfruimte minder is dan deze hoeveelheid",
"MovieIsOnImportExclusionList": "Film staat op de uitzonderingenlijst voor importeren",
"MovieTitleHelpText": "De titel van de uit te sluiten film (kan van alles zijn)",
"MovieYearHelpText": "Het jaar van de uit te sluiten film",
"MovieTitleToExcludeHelpText": "De titel van de uit te sluiten film (kan van alles zijn)",
"MovieYearToExcludeHelpText": "Het jaar van de uit te sluiten film",
"NotificationTriggers": "Melding Reactiestarters",
"OpenBrowserOnStart": "Open de browser bij het starten",
"NoLimitForAnyRuntime": "Geen limiet voor eender welke speelduur",
@ -554,7 +554,7 @@
"TestAllClients": "Test Alle Downloaders",
"TestAllLists": "Test Alle Lijsten",
"TMDBId": "TMDb Id",
"TmdbIdHelpText": "De TMDb Id van de uit te sluiten film",
"TmdbIdExcludeHelpText": "De TMDb Id van de uit te sluiten film",
"TorrentDelay": "Torrent Vertraging",
"Torrents": "Torrents",
"TotalFileSize": "Totale Bestandsgrootte",

@ -336,9 +336,9 @@
"MovieIsUnmonitored": "Film jest niemonitorowany",
"MoviesSelectedInterp": "Wybrane filmy: {0}",
"MovieTitle": "Tytuł filmu",
"MovieTitleHelpText": "Tytuł filmu do wykluczenia (może być dowolny znaczący)",
"MovieTitleToExcludeHelpText": "Tytuł filmu do wykluczenia (może być dowolny znaczący)",
"MovieYear": "Rok filmowy",
"MovieYearHelpText": "Rok filmu do wykluczenia",
"MovieYearToExcludeHelpText": "Rok filmu do wykluczenia",
"MustContain": "Musi zawierać",
"MustNotContain": "Nie może zawierać",
"NamingSettings": "Ustawienia nazewnictwa",
@ -859,7 +859,7 @@
"Title": "Tytuł",
"Titles": "Tytuły",
"TMDBId": "Identyfikator TMDb",
"TmdbIdHelpText": "Identyfikator TMDb filmu do wykluczenia",
"TmdbIdExcludeHelpText": "Identyfikator TMDb filmu do wykluczenia",
"Today": "Dzisiaj",
"TorrentDelay": "Opóźnienie Torrenta",
"TorrentDelayHelpText": "Opóźnienie w ciągu kilku minut, aby poczekać przed złapaniem torrenta",

@ -335,9 +335,9 @@
"NamingSettings": "Definições de nomenclatura",
"MustNotContain": "Não deve conter",
"MustContain": "Deve conter",
"MovieYearHelpText": "Ano do filme a eliminar",
"MovieYearToExcludeHelpText": "Ano do filme a eliminar",
"MovieYear": "Ano do filme",
"MovieTitleHelpText": "Título do filme a eliminar (pode ser qualquer palavra)",
"MovieTitleToExcludeHelpText": "Título do filme a eliminar (pode ser qualquer palavra)",
"MovieIsDownloading": "Transferindo filme",
"MovieInfoLanguageHelpTextWarning": "É preciso reiniciar o browser",
"MovieID": "ID do filme",
@ -569,7 +569,7 @@
"ExtraFileExtensionsHelpTextsExamples": "Exemplos: \".sub, .nfo\" ou \"sub,nfo\"",
"ExtraFileExtensionsHelpText": "Lista separada por vírgulas de ficheiros adicionais a importar (.nfo será importado como .nfo-orig)",
"ExistingTag": "Etiqueta existente",
"TmdbIdHelpText": "ID do TMDb do filme a eliminar",
"TmdbIdExcludeHelpText": "ID do TMDb do filme a eliminar",
"MovieExcludedFromAutomaticAdd": "Filme eliminado da adição automática",
"MovieAlreadyExcluded": "Filme já eliminado",
"ExcludeMovie": "Eliminar filme",

@ -462,9 +462,9 @@
"About": "Sobre",
"Analytics": "Análises",
"Month": "Mês",
"MovieYearHelpText": "O ano do filme a excluir",
"MovieYearToExcludeHelpText": "O ano do filme a excluir",
"MovieYear": "Ano do filme",
"MovieTitleHelpText": "O título do filme a excluir (pode ser qualquer coisa significativa)",
"MovieTitleToExcludeHelpText": "O título do filme a excluir (pode ser qualquer coisa significativa)",
"MovieTitle": "Título do filme",
"MoviesSelectedInterp": "{count} Filme(s) selecionado(s)",
"Movies": "Filmes",
@ -764,7 +764,7 @@
"Ui": "IU",
"Type": "Tipo",
"Torrents": "Torrents",
"TmdbIdHelpText": "A ID do TMDb do filme a excluir",
"TmdbIdExcludeHelpText": "A ID do TMDb do filme a excluir",
"TMDBId": "ID do TMDb",
"TMDb": "TMDb",
"SslCertPathHelpText": "Caminho para o arquivo pfx",

@ -519,12 +519,12 @@
"BranchUpdate": "Sucursală de utilizat pentru actualizarea {appName}",
"BranchUpdateMechanism": "Ramură utilizată de mecanismul extern de actualizare",
"BypassProxyForLocalAddresses": "Nu folosiți Proxy pentru adrese locale",
"MovieTitleHelpText": "Titlul filmului de exclus (poate avea orice sens)",
"MovieTitleToExcludeHelpText": "Titlul filmului de exclus (poate avea orice sens)",
"Local": "Local",
"MovieYear": "Anul filmului",
"RecyclingBinCleanup": "Curățarea coșului de reciclare",
"RefreshMovie": "Reîmprospătați filmul",
"MovieYearHelpText": "Anul filmului de exclus",
"MovieYearToExcludeHelpText": "Anul filmului de exclus",
"MustContain": "Trebuie sa contina",
"MustNotContain": "Nu trebuie să conțină",
"NamingSettings": "Setări de denumire",
@ -877,7 +877,7 @@
"TagIsNotUsedAndCanBeDeleted": "Eticheta nu este utilizată și poate fi ștearsă",
"ICalTagsMoviesHelpText": "Se aplică filmelor cu cel puțin o etichetă potrivită",
"TMDBId": "Cod TMDb",
"TmdbIdHelpText": "Codul TMDb al filmului de exclus",
"TmdbIdExcludeHelpText": "Codul TMDb al filmului de exclus",
"Today": "Astăzi",
"TorrentDelay": "Întârziere Torrent",
"TorrentDelayTime": "Întârziere Torrent: {0}",

@ -490,9 +490,9 @@
"MustNotContain": "Не должен содержать",
"MustContain": "Должен содержать",
"MultiLanguage": "Многоязычный",
"MovieYearHelpText": "Год фильма для исключений",
"MovieYearToExcludeHelpText": "Год фильма для исключений",
"MovieYear": "Год фильма",
"MovieTitleHelpText": "Название фильма для исключения (может быть любым)",
"MovieTitleToExcludeHelpText": "Название фильма для исключения (может быть любым)",
"MovieTitle": "Название фильма",
"MoviesSelectedInterp": "{count} фильм(ов) выбрано",
"Movies": "Фильмы",
@ -771,7 +771,7 @@
"TorrentDelay": "Задержка торрента",
"Tomorrow": "Завтра",
"Today": "Сегодня",
"TmdbIdHelpText": "Идентификатор TMDb фильма, который нужно исключить",
"TmdbIdExcludeHelpText": "Идентификатор TMDb фильма, который нужно исключить",
"TMDBId": "TMDb идентификатор",
"TMDb": "TMDb",
"SearchAll": "Искать все",

@ -709,9 +709,9 @@
"DatabaseMigration": "DB Migration",
"ImportExtraFiles": "Importera extra filer",
"ImportExtraFilesMovieHelpText": "Importera matchande extrafiler (undertexter, nfo, etc) efter import av en filmfil",
"MovieTitleHelpText": "Filmens titel att utesluta (kan vara något meningsfullt)",
"MovieTitleToExcludeHelpText": "Filmens titel att utesluta (kan vara något meningsfullt)",
"MinimumCustomFormatScoreHelpText": "Lägsta möjliga anpassade formatpoäng tillåtet att ladda ner",
"MovieYearHelpText": "Året för filmen att utesluta",
"MovieYearToExcludeHelpText": "Året för filmen att utesluta",
"ProxyType": "Proxy-typ",
"NamingSettings": "Namninställningar",
"LastDuration": "lastDuration",
@ -899,7 +899,7 @@
"TheLogLevelDefault": "Loggnivån är som standard 'Info' och kan ändras i",
"ThisCannotBeCancelled": "Detta kan inte avbrytas en gång startat utan att {appName} startas om.",
"TMDBId": "TMDb-id",
"TmdbIdHelpText": "TMDb-id för filmen att utesluta",
"TmdbIdExcludeHelpText": "TMDb-id för filmen att utesluta",
"TorrentDelay": "Torrentfördröjning",
"TorrentDelayHelpText": "Fördröja på några minuter för att vänta innan du tar en torrent",
"TorrentDelayTime": "Torrentfördröjning: {0}",

@ -169,7 +169,7 @@
"TagsSettingsSummary": "ดูแท็กทั้งหมดและวิธีการใช้งาน แท็กที่ไม่ได้ใช้สามารถลบออกได้",
"TheLogLevelDefault": "ระดับการบันทึกมีค่าเริ่มต้นเป็น \"ข้อมูล\" และสามารถเปลี่ยนแปลงได้",
"Titles": "ชื่อเรื่อง",
"TmdbIdHelpText": "TMDb Id ของภาพยนตร์ที่จะยกเว้น",
"TmdbIdExcludeHelpText": "TMDb Id ของภาพยนตร์ที่จะยกเว้น",
"TorrentsDisabled": "Torrents ถูกปิดใช้งาน",
"UiLanguageHelpText": "ภาษาที่ {appName} จะใช้สำหรับ UI",
"UiSettingsSummary": "ตัวเลือกปฏิทินวันที่และสีบกพร่อง",
@ -442,9 +442,9 @@
"Movies": "ภาพยนตร์",
"MoviesSelectedInterp": "{0} ภาพยนตร์ที่เลือก",
"MovieTitle": "ชื่อหนัง",
"MovieTitleHelpText": "ชื่อของภาพยนตร์ที่จะไม่รวม (อาจมีความหมายอะไรก็ได้)",
"MovieTitleToExcludeHelpText": "ชื่อของภาพยนตร์ที่จะไม่รวม (อาจมีความหมายอะไรก็ได้)",
"MovieYear": "ปีภาพยนตร์",
"MovieYearHelpText": "ปีของภาพยนตร์ที่จะไม่รวม",
"MovieYearToExcludeHelpText": "ปีของภาพยนตร์ที่จะไม่รวม",
"MustContain": "ต้องมี",
"MustNotContain": "ต้องไม่มี",
"NamingSettings": "การตั้งชื่อการตั้งค่า",

@ -299,7 +299,7 @@
"ThisCannotBeCancelled": "Bu, {appName} yeniden başlatılmadan başlatıldıktan sonra iptal edilemez.",
"Title": "Başlık",
"TMDBId": "TMDb Kimliği",
"TmdbIdHelpText": "Hariç tutulacak filmin TMDb Kimliği",
"TmdbIdExcludeHelpText": "Hariç tutulacak filmin TMDb Kimliği",
"Today": "Bugün",
"TorrentDelay": "Torrent Gecikmesi",
"TorrentDelayHelpText": "Bir torrent almadan önce beklemek için dakikalar içinde gecikme",
@ -571,9 +571,9 @@
"MovieIsDownloading": "Film indiriliyor",
"MovieIsUnmonitored": "Film takip edilmiyor",
"MoviesSelectedInterp": "{count} Film Seçildi",
"MovieTitleHelpText": "Hariç tutulacak filmin başlığı (anlamlı herhangi bir şey olabilir)",
"MovieTitleToExcludeHelpText": "Hariç tutulacak filmin başlığı (anlamlı herhangi bir şey olabilir)",
"MovieYear": "Film Yılı",
"MovieYearHelpText": "Hariç tutulacak film yılı",
"MovieYearToExcludeHelpText": "Hariç tutulacak film yılı",
"PendingChangesDiscardChanges": "Değişiklikleri atın ve ayrıl",
"PreferredSize": "Tercih Edilen Boyut",
"Proper": "Uygun",

@ -871,7 +871,7 @@
"Timeleft": "Час залишився",
"Title": "Назва",
"Titles": "Назви",
"TmdbIdHelpText": "Ідентифікатор TMDb фільму, який потрібно виключити",
"TmdbIdExcludeHelpText": "Ідентифікатор TMDb фільму, який потрібно виключити",
"TmdbRating": "Рейтинг TMDb",
"TmdbVotes": "Голоси TMDb",
"TorrentDelayTime": "Затримка торрента: {0}",
@ -941,9 +941,9 @@
"InstallLatest": "Встановити останній",
"MegabytesPerMinute": "Мегабайт за хвилину",
"Message": "Повідомлення",
"MovieTitleHelpText": "Назва фільму, який потрібно виключити (може бути будь-яким значущим)",
"MovieTitleToExcludeHelpText": "Назва фільму, який потрібно виключити (може бути будь-яким значущим)",
"MovieYear": "Фільм рік",
"MovieYearHelpText": "Рік фільму виключити",
"MovieYearToExcludeHelpText": "Рік фільму виключити",
"NegateHelpText": "Якщо позначено, настроюваний формат не застосовуватиметься, якщо ця умова {0} збігається.",
"Never": "Ніколи",
"New": "Новий",

@ -469,9 +469,9 @@
"AddMovies": "Thêm phim",
"MoviesSelectedInterp": "{0} Phim đã chọn",
"MovieTitle": "Tên phim",
"MovieTitleHelpText": "Tiêu đề của bộ phim cần loại trừ (có thể là bất kỳ điều gì có ý nghĩa)",
"MovieTitleToExcludeHelpText": "Tiêu đề của bộ phim cần loại trừ (có thể là bất kỳ điều gì có ý nghĩa)",
"MovieYear": "Năm phim",
"MovieYearHelpText": "Năm của bộ phim để loại trừ",
"MovieYearToExcludeHelpText": "Năm của bộ phim để loại trừ",
"MustContain": "Phải chứa",
"AddNewTmdbIdMessage": "Bạn cũng có thể tìm kiếm bằng cách sử dụng TMDb Id của một bộ phim. ví dụ. 'tmdb: 71663'",
"Indexer": "Người lập chỉ mục",
@ -886,7 +886,7 @@
"Time": "Thời gian",
"Title": "Tiêu đề",
"TMDBId": "Id TMDb",
"TmdbIdHelpText": "Id TMDb của phim để loại trừ",
"TmdbIdExcludeHelpText": "Id TMDb của phim để loại trừ",
"TorrentDelayTime": "Độ trễ Torrent: {0}",
"Torrents": "Torrents",
"Trailer": "Giới thiệu tóm tắt",

@ -837,7 +837,7 @@
"TagsSettingsSummary": "显示全部标签和标签使用情况,可删除未使用的标签",
"TMDb": "TMDb",
"TMDBId": "TMDb ID",
"TmdbIdHelpText": "排除电影的TMDb ID",
"TmdbIdExcludeHelpText": "排除电影的TMDb ID",
"TorrentDelay": "Torrent延时",
"TorrentDelayTime": "Torrent延时{0}",
"Torrents": "种子",
@ -901,8 +901,8 @@
"NextExecution": "接下来执行",
"New": "新的",
"NegateHelpText": "如勾选,当条件 {0} 满足时不会应用自定义格式。",
"MovieYearHelpText": "排除的电影的年份",
"MovieTitleHelpText": "要排除的电影标题(可以是任何字段)",
"MovieYearToExcludeHelpText": "排除的电影的年份",
"MovieTitleToExcludeHelpText": "要排除的电影标题(可以是任何字段)",
"MovieIsRecommend": "影片是根据最近添加的推荐",
"MovieIndexScrollTop": "影片索引:滚动到顶部",
"MovieIndexScrollBottom": "影片索引:滚动到底部",

@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentValidation;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Core.ImportLists.ImportExclusions;
using Radarr.Http;
using Radarr.Http.Extensions;
using Radarr.Http.REST;
using Radarr.Http.REST.Attributes;
@ -25,6 +27,7 @@ namespace Radarr.Api.V3.ImportLists
[HttpGet]
[Produces("application/json")]
[Obsolete("Deprecated")]
public List<ImportListExclusionResource> GetImportListExclusions()
{
return _importListExclusionService.All().ToResource();
@ -35,6 +38,16 @@ namespace Radarr.Api.V3.ImportLists
return _importListExclusionService.Get(id).ToResource();
}
[HttpGet("paged")]
[Produces("application/json")]
public PagingResource<ImportListExclusionResource> GetImportListExclusionsPaged([FromQuery] PagingRequestResource paging)
{
var pagingResource = new PagingResource<ImportListExclusionResource>(paging);
var pageSpec = pagingResource.MapToPagingSpec<ImportListExclusionResource, ImportListExclusion>();
return pageSpec.ApplyToPage(_importListExclusionService.Paged, ImportListExclusionResourceMapper.ToResource);
}
[RestPostById]
[Consumes("application/json")]
public ActionResult<ImportListExclusionResource> AddImportListExclusion([FromBody] ImportListExclusionResource resource)

Loading…
Cancel
Save