Fixed: Minor improvements and translations for managing bulk indexers, lists and clients

This commit is contained in:
Bogdan 2023-07-10 22:37:47 +03:00
parent 7dde88387a
commit 48b9c1e8b9
21 changed files with 208 additions and 100 deletions

View File

@ -86,7 +86,7 @@ class DownloadClientSettings extends Component {
/>
<PageToolbarButton
label="Manage Clients"
label={translate('ManageClients')}
iconName={icons.MANAGE}
onPress={this.onManageDownloadClientsPress}
/>

View File

@ -27,9 +27,9 @@ interface ManageDownloadClientsEditModalContentProps {
const NO_CHANGE = 'noChange';
const enableOptions = [
{ key: NO_CHANGE, value: 'No Change', disabled: true },
{ key: 'enabled', value: 'Enabled' },
{ key: 'disabled', value: 'Disabled' },
{ key: NO_CHANGE, value: translate('NoChange'), disabled: true },
{ key: 'enabled', value: translate('Enabled') },
{ key: 'disabled', value: translate('Disabled') },
];
function ManageDownloadClientsEditModalContent(
@ -97,7 +97,9 @@ function ManageDownloadClientsEditModalContent(
setRemoveFailedDownloads(value);
break;
default:
console.warn('EditDownloadClientsModalContent Unknown Input');
console.warn(
`EditDownloadClientsModalContent Unknown Input: '${name}'`
);
}
},
[]
@ -162,9 +164,7 @@ function ManageDownloadClientsEditModalContent(
<ModalFooter className={styles.modalFooter}>
<div className={styles.selected}>
{translate('{count} download clients selected', {
count: selectedCount,
})}
{translate('CountDownloadClientsSelected', [selectedCount])}
</div>
<div>

View File

@ -1,6 +1,7 @@
import React, { useCallback, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { DownloadClientAppState } from 'App/State/SettingsAppState';
import Alert from 'Components/Alert';
import Button from 'Components/Link/Button';
import SpinnerButton from 'Components/Link/SpinnerButton';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
@ -20,6 +21,7 @@ import {
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import { SelectStateInputProps } from 'typings/props';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
import ManageDownloadClientsEditModal from './Edit/ManageDownloadClientsEditModal';
import ManageDownloadClientsModalRow from './ManageDownloadClientsModalRow';
@ -33,37 +35,37 @@ type OnSelectedChangeCallback = React.ComponentProps<
const COLUMNS = [
{
name: 'name',
label: 'Name',
label: translate('Name'),
isSortable: true,
isVisible: true,
},
{
name: 'implementation',
label: 'Implementation',
label: translate('Implementation'),
isSortable: true,
isVisible: true,
},
{
name: 'enable',
label: 'Enabled',
label: translate('Enabled'),
isSortable: true,
isVisible: true,
},
{
name: 'priority',
label: 'Priority',
label: translate('Priority'),
isSortable: true,
isVisible: true,
},
{
name: 'removeCompletedDownloads',
label: 'Remove Completed',
label: translate('RemoveCompleted'),
isSortable: true,
isVisible: true,
},
{
name: 'removeFailedDownloads',
label: 'Remove Failed',
label: translate('RemoveFailed'),
isSortable: true,
isVisible: true,
},
@ -158,17 +160,24 @@ function ManageDownloadClientsModalContent(
[items, setSelectState]
);
const errorMessage = getErrorMessage(error, 'Unable to load import lists.');
const errorMessage = getErrorMessage(
error,
'Unable to load download clients.'
);
const anySelected = selectedCount > 0;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>Manage Import Lists</ModalHeader>
<ModalHeader>{translate('ManageDownloadClients')}</ModalHeader>
<ModalBody>
{isFetching ? <LoadingIndicator /> : null}
{error ? <div>{errorMessage}</div> : null}
{isPopulated && !error && !items.length && (
<Alert kind={kinds.INFO}>{translate('NoDownloadClientsFound')}</Alert>
)}
{isPopulated && !!items.length && !isFetching && !isFetching ? (
<Table
columns={COLUMNS}
@ -203,7 +212,7 @@ function ManageDownloadClientsModalContent(
isDisabled={!anySelected}
onPress={onDeletePress}
>
Delete
{translate('Delete')}
</SpinnerButton>
<SpinnerButton
@ -211,11 +220,11 @@ function ManageDownloadClientsModalContent(
isDisabled={!anySelected}
onPress={onEditPress}
>
Edit
{translate('Edit')}
</SpinnerButton>
</div>
<Button onPress={onModalClose}>Close</Button>
<Button onPress={onModalClose}>{translate('Close')}</Button>
</ModalFooter>
<ManageDownloadClientsEditModal
@ -228,9 +237,11 @@ function ManageDownloadClientsModalContent(
<ConfirmModal
isOpen={isDeleteModalOpen}
kind={kinds.DANGER}
title="Delete Download Clients(s)"
message={`Are you sure you want to delete ${selectedIds.length} download clients(s)?`}
confirmLabel="Delete"
title={translate('DeleteSelectedDownloadClients')}
message={translate('DeleteSelectedDownloadClientsMessageText', [
selectedIds.length,
])}
confirmLabel={translate('Delete')}
onConfirm={onConfirmDelete}
onCancel={onDeleteModalClose}
/>

View File

@ -1,9 +1,12 @@
import React, { useCallback } from 'react';
import Label from 'Components/Label';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import Column from 'Components/Table/Column';
import TableRow from 'Components/Table/TableRow';
import { kinds } from 'Helpers/Props';
import { SelectStateInputProps } from 'typings/props';
import translate from 'Utilities/String/translate';
import styles from './ManageDownloadClientsModalRow.css';
interface ManageDownloadClientsModalRowProps {
@ -58,17 +61,19 @@ function ManageDownloadClientsModalRow(
</TableRowCell>
<TableRowCell className={styles.enable}>
{enable ? 'Yes' : 'No'}
<Label kind={enable ? kinds.SUCCESS : kinds.DISABLED} outline={!enable}>
{enable ? translate('Yes') : translate('No')}
</Label>
</TableRowCell>
<TableRowCell className={styles.priority}>{priority}</TableRowCell>
<TableRowCell className={styles.removeCompletedDownloads}>
{removeCompletedDownloads ? 'Yes' : 'No'}
{removeCompletedDownloads ? translate('Yes') : translate('No')}
</TableRowCell>
<TableRowCell className={styles.removeFailedDownloads}>
{removeFailedDownloads ? 'Yes' : 'No'}
{removeFailedDownloads ? translate('Yes') : translate('No')}
</TableRowCell>
</TableRow>
);

View File

@ -86,7 +86,7 @@ class ImportListSettings extends Component {
/>
<PageToolbarButton
label="Manage Lists"
label={translate('ManageLists')}
iconName={icons.MANAGE}
onPress={this.onManageImportListsPress}
/>

View File

@ -26,9 +26,9 @@ interface ManageImportListsEditModalContentProps {
const NO_CHANGE = 'noChange';
const autoAddOptions = [
{ key: NO_CHANGE, value: 'No Change', disabled: true },
{ key: 'enabled', value: 'Enabled' },
{ key: 'disabled', value: 'Disabled' },
{ key: NO_CHANGE, value: translate('NoChange'), disabled: true },
{ key: 'enabled', value: translate('Enabled') },
{ key: 'disabled', value: translate('Disabled') },
];
function ManageImportListsEditModalContent(
@ -36,7 +36,7 @@ function ManageImportListsEditModalContent(
) {
const { importListIds, onSavePress, onModalClose } = props;
const [enableAuto, setenableAuto] = useState(NO_CHANGE);
const [enableAuto, setEnableAuto] = useState(NO_CHANGE);
const [qualityProfileId, setQualityProfileId] = useState<string | number>(
NO_CHANGE
);
@ -72,7 +72,7 @@ function ManageImportListsEditModalContent(
({ name, value }: { name: string; value: string }) => {
switch (name) {
case 'enableAuto':
setenableAuto(value);
setEnableAuto(value);
break;
case 'qualityProfileId':
setQualityProfileId(value);
@ -81,7 +81,7 @@ function ManageImportListsEditModalContent(
setRootFolderPath(value);
break;
default:
console.warn('EditImportListModalContent Unknown Input');
console.warn(`EditImportListModalContent Unknown Input: '${name}'`);
}
},
[]
@ -136,7 +136,7 @@ function ManageImportListsEditModalContent(
<ModalFooter className={styles.modalFooter}>
<div className={styles.selected}>
{translate('{count} import lists selected', { count: selectedCount })}
{translate('CountImportListsSelected', [selectedCount])}
</div>
<div>

View File

@ -1,6 +1,7 @@
import React, { useCallback, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { ImportListAppState } from 'App/State/SettingsAppState';
import Alert from 'Components/Alert';
import Button from 'Components/Link/Button';
import SpinnerButton from 'Components/Link/SpinnerButton';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
@ -20,6 +21,7 @@ import {
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import { SelectStateInputProps } from 'typings/props';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
import ManageImportListsEditModal from './Edit/ManageImportListsEditModal';
import ManageImportListsModalRow from './ManageImportListsModalRow';
@ -34,37 +36,37 @@ type OnSelectedChangeCallback = React.ComponentProps<
const COLUMNS = [
{
name: 'name',
label: 'Name',
label: translate('Name'),
isSortable: true,
isVisible: true,
},
{
name: 'implementation',
label: 'Implementation',
label: translate('Implementation'),
isSortable: true,
isVisible: true,
},
{
name: 'qualityProfileId',
label: 'Quality Profile',
label: translate('QualityProfile'),
isSortable: true,
isVisible: true,
},
{
name: 'rootFolderPath',
label: 'Root Folder',
label: translate('RootFolder'),
isSortable: true,
isVisible: true,
},
{
name: 'enableAuto',
label: 'Auto Add',
label: translate('AutomaticAdd'),
isSortable: true,
isVisible: true,
},
{
name: 'tags',
label: 'Tags',
label: translate('Tags'),
isSortable: true,
isVisible: true,
},
@ -190,12 +192,16 @@ function ManageImportListsModalContent(
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>Manage Import Lists</ModalHeader>
<ModalHeader>{translate('ManageImportLists')}</ModalHeader>
<ModalBody>
{isFetching ? <LoadingIndicator /> : null}
{error ? <div>{errorMessage}</div> : null}
{isPopulated && !error && !items.length && (
<Alert kind={kinds.INFO}>{translate('NoImportListsFound')}</Alert>
)}
{isPopulated && !!items.length && !isFetching && !isFetching ? (
<Table
columns={COLUMNS}
@ -230,7 +236,7 @@ function ManageImportListsModalContent(
isDisabled={!anySelected}
onPress={onDeletePress}
>
Delete
{translate('Delete')}
</SpinnerButton>
<SpinnerButton
@ -238,7 +244,7 @@ function ManageImportListsModalContent(
isDisabled={!anySelected}
onPress={onEditPress}
>
Edit
{translate('Edit')}
</SpinnerButton>
<SpinnerButton
@ -246,11 +252,11 @@ function ManageImportListsModalContent(
isDisabled={!anySelected}
onPress={onTagsPress}
>
Set Tags
{translate('SetTags')}
</SpinnerButton>
</div>
<Button onPress={onModalClose}>Close</Button>
<Button onPress={onModalClose}>{translate('Close')}</Button>
</ModalFooter>
<ManageImportListsEditModal
@ -270,9 +276,11 @@ function ManageImportListsModalContent(
<ConfirmModal
isOpen={isDeleteModalOpen}
kind={kinds.DANGER}
title="Delete Import List(s)"
message={`Are you sure you want to delete ${selectedIds.length} import list(s)?`}
confirmLabel="Delete"
title={translate('DeleteSelectedImportLists')}
message={translate('DeleteSelectedImportListsMessageText', [
selectedIds.length,
])}
confirmLabel={translate('Delete')}
onConfirm={onConfirmDelete}
onCancel={onDeleteModalClose}
/>

View File

@ -17,6 +17,7 @@ import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
import ImportList from 'typings/ImportList';
import translate from 'Utilities/String/translate';
import styles from './TagsModalContent.css';
interface TagsModalContentProps {
@ -36,7 +37,7 @@ function TagsModalContent(props: TagsModalContentProps) {
const [tags, setTags] = useState<number[]>([]);
const [applyTags, setApplyTags] = useState('add');
const seriesTags = useMemo(() => {
const importListsTags = useMemo(() => {
const tags = ids.reduce((acc: number[], id) => {
const s = allImportLists.items.find((s: ImportList) => s.id === id);
@ -69,19 +70,19 @@ function TagsModalContent(props: TagsModalContentProps) {
}, [tags, applyTags, onApplyTagsPress]);
const applyTagsOptions = [
{ key: 'add', value: 'Add' },
{ key: 'remove', value: 'Remove' },
{ key: 'replace', value: 'Replace' },
{ key: 'add', value: translate('Add') },
{ key: 'remove', value: translate('Remove') },
{ key: 'replace', value: translate('Replace') },
];
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>Tags</ModalHeader>
<ModalHeader>{translate('Tags')}</ModalHeader>
<ModalBody>
<Form>
<FormGroup>
<FormLabel>Tags</FormLabel>
<FormLabel>{translate('Tags')}</FormLabel>
<FormInputGroup
type={inputTypes.TAG}
@ -92,7 +93,7 @@ function TagsModalContent(props: TagsModalContentProps) {
</FormGroup>
<FormGroup>
<FormLabel>Apply Tags</FormLabel>
<FormLabel>{translate('ApplyTags')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
@ -100,20 +101,20 @@ function TagsModalContent(props: TagsModalContentProps) {
value={applyTags}
values={applyTagsOptions}
helpTexts={[
'How to apply tags to the selected list',
'Add: Add the tags the existing list of tags',
'Remove: Remove the entered tags',
'Replace: Replace the tags with the entered tags (enter no tags to clear all tags)',
translate('ApplyTagsHelpTexts1'),
translate('ApplyTagsHelpTexts2'),
translate('ApplyTagsHelpTexts3'),
translate('ApplyTagsHelpTexts4'),
]}
onChange={onApplyTagsChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Result</FormLabel>
<FormLabel>{translate('Result')}</FormLabel>
<div className={styles.result}>
{seriesTags.map((id) => {
{importListsTags.map((id) => {
const tag = tagList.find((t) => t.id === id);
if (!tag) {
@ -127,7 +128,11 @@ function TagsModalContent(props: TagsModalContentProps) {
return (
<Label
key={tag.id}
title={removeTag ? 'Removing tag' : 'Existing tag'}
title={
removeTag
? translate('RemovingTag')
: translate('ExistingTag')
}
kind={removeTag ? kinds.INVERSE : kinds.INFO}
size={sizes.LARGE}
>
@ -144,14 +149,14 @@ function TagsModalContent(props: TagsModalContentProps) {
return null;
}
if (seriesTags.indexOf(id) > -1) {
if (importListsTags.indexOf(id) > -1) {
return null;
}
return (
<Label
key={tag.id}
title={'Adding tag'}
title={translate('AddingTag')}
kind={kinds.SUCCESS}
size={sizes.LARGE}
>
@ -165,10 +170,10 @@ function TagsModalContent(props: TagsModalContentProps) {
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>Cancel</Button>
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
<Button kind={kinds.PRIMARY} onPress={onApplyPress}>
Apply
{translate('Apply')}
</Button>
</ModalFooter>
</ModalContent>

View File

@ -86,7 +86,7 @@ class IndexerSettings extends Component {
/>
<PageToolbarButton
label="Manage Indexers"
label={translate('ManageIndexers')}
iconName={icons.MANAGE}
onPress={this.onManageIndexersPress}
/>

View File

@ -1,6 +1,7 @@
import React, { useCallback, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { IndexerAppState } from 'App/State/SettingsAppState';
import Alert from 'Components/Alert';
import Button from 'Components/Link/Button';
import SpinnerButton from 'Components/Link/SpinnerButton';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
@ -201,6 +202,10 @@ function ManageIndexersModalContent(props: ManageIndexersModalContentProps) {
{error ? <div>{errorMessage}</div> : null}
{isPopulated && !error && !items.length && (
<Alert kind={kinds.INFO}>{translate('NoIndexersFound')}</Alert>
)}
{isPopulated && !!items.length && !isFetching && !isFetching ? (
<Table
columns={COLUMNS}
@ -275,8 +280,10 @@ function ManageIndexersModalContent(props: ManageIndexersModalContentProps) {
<ConfirmModal
isOpen={isDeleteModalOpen}
kind={kinds.DANGER}
title={translate('DeleteIndexers')}
message={translate('DeleteIndexersMessageText', [selectedIds.length])}
title={translate('DeleteSelectedIndexers')}
message={translate('DeleteSelectedIndexersMessageText', [
selectedIds.length,
])}
confirmLabel={translate('Delete')}
onConfirm={onConfirmDelete}
onCancel={onDeleteModalClose}

View File

@ -1,9 +1,11 @@
import React, { useCallback } from 'react';
import Label from 'Components/Label';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import Column from 'Components/Table/Column';
import TableRow from 'Components/Table/TableRow';
import TagListConnector from 'Components/TagListConnector';
import { kinds } from 'Helpers/Props';
import { SelectStateInputProps } from 'typings/props';
import translate from 'Utilities/String/translate';
import styles from './ManageIndexersModalRow.css';
@ -60,15 +62,30 @@ function ManageIndexersModalRow(props: ManageIndexersModalRowProps) {
</TableRowCell>
<TableRowCell className={styles.enableRss}>
{enableRss ? translate('Yes') : translate('No')}
<Label
kind={enableRss ? kinds.SUCCESS : kinds.DISABLED}
outline={!enableRss}
>
{enableRss ? translate('Yes') : translate('No')}
</Label>
</TableRowCell>
<TableRowCell className={styles.enableAutomaticSearch}>
{enableAutomaticSearch ? translate('Yes') : translate('No')}
<Label
kind={enableAutomaticSearch ? kinds.SUCCESS : kinds.DISABLED}
outline={!enableAutomaticSearch}
>
{enableAutomaticSearch ? translate('Yes') : translate('No')}
</Label>
</TableRowCell>
<TableRowCell className={styles.enableInteractiveSearch}>
{enableInteractiveSearch ? translate('Yes') : translate('No')}
<Label
kind={enableInteractiveSearch ? kinds.SUCCESS : kinds.DISABLED}
outline={!enableInteractiveSearch}
>
{enableInteractiveSearch ? translate('Yes') : translate('No')}
</Label>
</TableRowCell>
<TableRowCell className={styles.priority}>{priority}</TableRowCell>

View File

@ -37,7 +37,7 @@ function TagsModalContent(props: TagsModalContentProps) {
const [tags, setTags] = useState<number[]>([]);
const [applyTags, setApplyTags] = useState('add');
const seriesTags = useMemo(() => {
const indexersTags = useMemo(() => {
const tags = ids.reduce((acc: number[], id) => {
const s = allIndexers.items.find((s: Indexer) => s.id === id);
@ -101,10 +101,10 @@ function TagsModalContent(props: TagsModalContentProps) {
value={applyTags}
values={applyTagsOptions}
helpTexts={[
'How to apply tags to the selected indexer(s)',
'Add: Add the tags the existing list of tags',
'Remove: Remove the entered tags',
'Replace: Replace the tags with the entered tags (enter no tags to clear all tags)',
translate('ApplyTagsHelpTexts1'),
translate('ApplyTagsHelpTexts2'),
translate('ApplyTagsHelpTexts3'),
translate('ApplyTagsHelpTexts4'),
]}
onChange={onApplyTagsChange}
/>
@ -114,7 +114,7 @@ function TagsModalContent(props: TagsModalContentProps) {
<FormLabel>{translate('Result')}</FormLabel>
<div className={styles.result}>
{seriesTags.map((id) => {
{indexersTags.map((id) => {
const tag = tagList.find((t) => t.id === id);
if (!tag) {
@ -149,7 +149,7 @@ function TagsModalContent(props: TagsModalContentProps) {
return null;
}
if (seriesTags.indexOf(id) > -1) {
if (indexersTags.indexOf(id) > -1) {
return null;
}

View File

@ -71,6 +71,7 @@
"AutoRedownloadFailedHelpText": "Automatically search for and attempt to download a different release",
"AutoUnmonitorPreviouslyDownloadedMoviesHelpText": "Movies deleted from the disk are automatically unmonitored in Radarr",
"Automatic": "Automatic",
"AutomaticAdd": "Automatic Add",
"AutomaticSearch": "Automatic Search",
"AvailabilityDelay": "Availability Delay",
"AvailabilityDelayHelpText": "Amount of time before or after available date to search for Movie",
@ -161,7 +162,9 @@
"CopyUsingHardlinksHelpTextWarning": "Occasionally, file locks may prevent renaming files that are being seeded. You may temporarily disable seeding and use Radarr's rename function as a work around.",
"CouldNotConnectSignalR": "Could not connect to SignalR, UI won't update",
"CouldNotFindResults": "Couldn't find any results for '{0}'",
"CountIndexersSelected": "{0} indexers selected",
"CountDownloadClientsSelected": "{0} download client(s) selected",
"CountImportListsSelected": "{0} import list(s) selected",
"CountIndexersSelected": "{0} indexer(s) selected",
"CreateEmptyMovieFolders": "Create empty movie folders",
"CreateEmptyMovieFoldersHelpText": "Create missing movie folders during disk scan",
"CreateGroup": "Create group",
@ -230,6 +233,12 @@
"DeleteRemotePathMappingMessageText": "Are you sure you want to delete this remote path mapping?",
"DeleteRestriction": "Delete Restriction",
"DeleteRestrictionHelpText": "Are you sure you want to delete this restriction?",
"DeleteSelectedDownloadClients": "Delete Download Client(s)",
"DeleteSelectedDownloadClientsMessageText": "Are you sure you want to delete {0} selected download client(s)?",
"DeleteSelectedImportLists": "Delete Import List(s)",
"DeleteSelectedImportListsMessageText": "Are you sure you want to delete {0} selected import list(s)?",
"DeleteSelectedIndexers": "Delete Indexer(s)",
"DeleteSelectedIndexersMessageText": "Are you sure you want to delete {0} selected indexer(s)?",
"DeleteSelectedMovie": "Delete Selected Movie(s)",
"DeleteSelectedMovieFiles": "Delete Selected Movie Files",
"DeleteSelectedMovieFilesMessage": "Are you sure you want to delete the selected movie files?",
@ -295,6 +304,8 @@
"EditQualityProfile": "Edit Quality Profile",
"EditRemotePathMapping": "Edit Remote Path Mapping",
"EditRestriction": "Edit Restriction",
"EditSelectedDownloadClients": "Edit Selected Download Clients",
"EditSelectedImportLists": "Edit Selected Import Lists",
"EditSelectedIndexers": "Edit Selected Indexers",
"EditSelectedMovies": "Edit Selected Movies",
"Edition": "Edition",
@ -406,6 +417,7 @@
"Images": "Images",
"ImdbRating": "IMDb Rating",
"ImdbVotes": "IMDb Votes",
"Implementation": "Implementation",
"Import": "Import",
"ImportCustomFormat": "Import Custom Format",
"ImportErrors": "Import Errors",
@ -514,7 +526,11 @@
"LowerCase": "Lowercase",
"MIA": "MIA",
"MaintenanceRelease": "Maintenance Release: bug fixes and other improvements. See Github Commit History for more details",
"ManageClients": "Manage Clients",
"ManageDownloadClients": "Manage Download Clients",
"ManageImportLists": "Manage Import Lists",
"ManageIndexers": "Manage Indexers",
"ManageLists": "Manage Lists",
"Manual": "Manual",
"ManualImport": "Manual Import",
"ManualImportSelectLanguage": "Manual Import - Select Language",
@ -632,9 +648,12 @@
"NoChange": "No Change",
"NoChanges": "No Changes",
"NoCollections": "No collections found, to get started you'll want to add a new movie, or import some existing ones",
"NoDownloadClientsFound": "No download clients found",
"NoEventsFound": "No events found",
"NoHistory": "No history",
"NoHistoryBlocklist": "No history blocklist",
"NoImportListsFound": "No import lists found",
"NoIndexersFound": "No indexers found",
"NoLeaveIt": "No, Leave It",
"NoLimitForAnyRuntime": "No limit for any runtime",
"NoLinks": "No Links",

View File

@ -6,8 +6,8 @@ namespace Radarr.Api.V3.DownloadClient
[V3ApiController]
public class DownloadClientController : ProviderControllerBase<DownloadClientResource, DownloadClientBulkResource, IDownloadClient, DownloadClientDefinition>
{
public static readonly DownloadClientResourceMapper ResourceMapper = new DownloadClientResourceMapper();
public static readonly DownloadClientBulkResourceMapper BulkResourceMapper = new DownloadClientBulkResourceMapper();
public static readonly DownloadClientResourceMapper ResourceMapper = new ();
public static readonly DownloadClientBulkResourceMapper BulkResourceMapper = new ();
public DownloadClientController(IDownloadClientFactory downloadClientFactory)
: base(downloadClientFactory, "downloadclient", ResourceMapper, BulkResourceMapper)

View File

@ -9,8 +9,8 @@ namespace Radarr.Api.V3.ImportLists
[V3ApiController]
public class ImportListController : ProviderControllerBase<ImportListResource, ImportListBulkResource, IImportList, ImportListDefinition>
{
public static readonly ImportListResourceMapper ResourceMapper = new ImportListResourceMapper();
public static readonly ImportListBulkResourceMapper BulkResourceMapper = new ImportListBulkResourceMapper();
public static readonly ImportListResourceMapper ResourceMapper = new ();
public static readonly ImportListBulkResourceMapper BulkResourceMapper = new ();
public ImportListController(IImportListFactory importListFactory, ProfileExistsValidator profileExistsValidator)
: base(importListFactory, "importlist", ResourceMapper, BulkResourceMapper)

View File

@ -6,8 +6,8 @@ namespace Radarr.Api.V3.Indexers
[V3ApiController]
public class IndexerController : ProviderControllerBase<IndexerResource, IndexerBulkResource, IIndexer, IndexerDefinition>
{
public static readonly IndexerResourceMapper ResourceMapper = new IndexerResourceMapper();
public static readonly IndexerBulkResourceMapper BulkResourceMapper = new IndexerBulkResourceMapper();
public static readonly IndexerResourceMapper ResourceMapper = new ();
public static readonly IndexerBulkResourceMapper BulkResourceMapper = new ();
public IndexerController(IndexerFactory indexerFactory)
: base(indexerFactory, "indexer", ResourceMapper, BulkResourceMapper)

View File

@ -1,3 +1,5 @@
using System;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Core.Extras.Metadata;
using Radarr.Http;
@ -6,12 +8,24 @@ namespace Radarr.Api.V3.Metadata
[V3ApiController]
public class MetadataController : ProviderControllerBase<MetadataResource, MetadataBulkResource, IMetadata, MetadataDefinition>
{
public static readonly MetadataResourceMapper ResourceMapper = new MetadataResourceMapper();
public static readonly MetadataBulkResourceMapper BulkResourceMapper = new MetadataBulkResourceMapper();
public static readonly MetadataResourceMapper ResourceMapper = new ();
public static readonly MetadataBulkResourceMapper BulkResourceMapper = new ();
public MetadataController(IMetadataFactory metadataFactory)
: base(metadataFactory, "metadata", ResourceMapper, BulkResourceMapper)
{
}
[NonAction]
public override ActionResult<MetadataResource> UpdateProvider([FromBody] MetadataBulkResource providerResource)
{
throw new NotImplementedException();
}
[NonAction]
public override object DeleteProviders([FromBody] MetadataBulkResource resource)
{
throw new NotImplementedException();
}
}
}

View File

@ -16,11 +16,4 @@ namespace Radarr.Api.V3.Movies
public bool DeleteFiles { get; set; }
public bool AddImportExclusion { get; set; }
}
public enum ApplyTags
{
Add,
Remove,
Replace
}
}

View File

@ -1,3 +1,5 @@
using System;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Core.Notifications;
using Radarr.Http;
@ -6,12 +8,24 @@ namespace Radarr.Api.V3.Notifications
[V3ApiController]
public class NotificationController : ProviderControllerBase<NotificationResource, NotificationBulkResource, INotification, NotificationDefinition>
{
public static readonly NotificationResourceMapper ResourceMapper = new NotificationResourceMapper();
public static readonly NotificationBulkResourceMapper BulkResourceMapper = new NotificationBulkResourceMapper();
public static readonly NotificationResourceMapper ResourceMapper = new ();
public static readonly NotificationBulkResourceMapper BulkResourceMapper = new ();
public NotificationController(NotificationFactory notificationFactory)
: base(notificationFactory, "notification", ResourceMapper, BulkResourceMapper)
{
}
[NonAction]
public override ActionResult<NotificationResource> UpdateProvider([FromBody] NotificationBulkResource providerResource)
{
throw new NotImplementedException();
}
[NonAction]
public override object DeleteProviders([FromBody] NotificationBulkResource resource)
{
throw new NotImplementedException();
}
}
}

View File

@ -1,6 +1,5 @@
using System.Collections.Generic;
using NzbDrone.Core.ThingiProvider;
using Radarr.Api.V3.Movies;
namespace Radarr.Api.V3
{
@ -9,6 +8,18 @@ namespace Radarr.Api.V3
public List<int> Ids { get; set; }
public List<int> Tags { get; set; }
public ApplyTags ApplyTags { get; set; }
public ProviderBulkResource()
{
Ids = new List<int>();
}
}
public enum ApplyTags
{
Add,
Remove,
Replace
}
public class ProviderBulkResourceMapper<TProviderBulkResource, TProviderDefinition>

View File

@ -6,7 +6,6 @@ using Microsoft.AspNetCore.Mvc;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
using Radarr.Api.V3.Movies;
using Radarr.Http.REST;
using Radarr.Http.REST.Attributes;
@ -103,8 +102,13 @@ namespace Radarr.Api.V3
[HttpPut("bulk")]
[Consumes("application/json")]
[Produces("application/json")]
public ActionResult<TProviderResource> UpdateProvider([FromBody] TBulkProviderResource providerResource)
public virtual ActionResult<TProviderResource> UpdateProvider([FromBody] TBulkProviderResource providerResource)
{
if (!providerResource.Ids.Any())
{
throw new BadRequestException("ids must be provided");
}
var definitionsToUpdate = _providerFactory.Get(providerResource.Ids).ToList();
foreach (var definition in definitionsToUpdate)
@ -157,7 +161,7 @@ namespace Radarr.Api.V3
[HttpDelete("bulk")]
[Consumes("application/json")]
public object DeleteProviders([FromBody] TBulkProviderResource resource)
public virtual object DeleteProviders([FromBody] TBulkProviderResource resource)
{
_providerFactory.Delete(resource.Ids);