mirror of
https://github.com/Radarr/Radarr.git
synced 2024-11-20 01:42:35 +01:00
New: Bulk manage custom formats
This commit is contained in:
parent
672b351497
commit
da5323a08f
@ -6,6 +6,7 @@ import AppSectionState, {
|
|||||||
PagedAppSectionState,
|
PagedAppSectionState,
|
||||||
} from 'App/State/AppSectionState';
|
} from 'App/State/AppSectionState';
|
||||||
import Language from 'Language/Language';
|
import Language from 'Language/Language';
|
||||||
|
import CustomFormat from 'typings/CustomFormat';
|
||||||
import DownloadClient from 'typings/DownloadClient';
|
import DownloadClient from 'typings/DownloadClient';
|
||||||
import ImportList from 'typings/ImportList';
|
import ImportList from 'typings/ImportList';
|
||||||
import ImportListExclusion from 'typings/ImportListExclusion';
|
import ImportListExclusion from 'typings/ImportListExclusion';
|
||||||
@ -39,6 +40,11 @@ export interface QualityProfilesAppState
|
|||||||
extends AppSectionState<QualityProfile>,
|
extends AppSectionState<QualityProfile>,
|
||||||
AppSectionSchemaState<QualityProfile> {}
|
AppSectionSchemaState<QualityProfile> {}
|
||||||
|
|
||||||
|
export interface CustomFormatAppState
|
||||||
|
extends AppSectionState<CustomFormat>,
|
||||||
|
AppSectionDeleteState,
|
||||||
|
AppSectionSaveState {}
|
||||||
|
|
||||||
export interface ImportListOptionsSettingsAppState
|
export interface ImportListOptionsSettingsAppState
|
||||||
extends AppSectionItemState<ImportListOptionsSettings>,
|
extends AppSectionItemState<ImportListOptionsSettings>,
|
||||||
AppSectionSaveState {}
|
AppSectionSaveState {}
|
||||||
@ -57,6 +63,7 @@ export type UiSettingsAppState = AppSectionItemState<UiSettings>;
|
|||||||
|
|
||||||
interface SettingsAppState {
|
interface SettingsAppState {
|
||||||
advancedSettings: boolean;
|
advancedSettings: boolean;
|
||||||
|
customFormats: CustomFormatAppState;
|
||||||
downloadClients: DownloadClientAppState;
|
downloadClients: DownloadClientAppState;
|
||||||
importListExclusions: ImportListExclusionsSettingsAppState;
|
importListExclusions: ImportListExclusionsSettingsAppState;
|
||||||
importListOptions: ImportListOptionsSettingsAppState;
|
importListOptions: ImportListOptionsSettingsAppState;
|
||||||
|
@ -8,6 +8,7 @@ import ParseToolbarButton from 'Parse/ParseToolbarButton';
|
|||||||
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
|
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import CustomFormatsConnector from './CustomFormats/CustomFormatsConnector';
|
import CustomFormatsConnector from './CustomFormats/CustomFormatsConnector';
|
||||||
|
import ManageCustomFormatsToolbarButton from './CustomFormats/Manage/ManageCustomFormatsToolbarButton';
|
||||||
|
|
||||||
function CustomFormatSettingsPage() {
|
function CustomFormatSettingsPage() {
|
||||||
return (
|
return (
|
||||||
@ -21,6 +22,8 @@ function CustomFormatSettingsPage() {
|
|||||||
<PageToolbarSeparator />
|
<PageToolbarSeparator />
|
||||||
|
|
||||||
<ParseToolbarButton />
|
<ParseToolbarButton />
|
||||||
|
|
||||||
|
<ManageCustomFormatsToolbarButton />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Modal from 'Components/Modal/Modal';
|
||||||
|
import ManageCustomFormatsEditModalContent from './ManageCustomFormatsEditModalContent';
|
||||||
|
|
||||||
|
interface ManageCustomFormatsEditModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
customFormatIds: number[];
|
||||||
|
onSavePress(payload: object): void;
|
||||||
|
onModalClose(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ManageCustomFormatsEditModal(
|
||||||
|
props: ManageCustomFormatsEditModalProps
|
||||||
|
) {
|
||||||
|
const { isOpen, customFormatIds, onSavePress, onModalClose } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={isOpen} onModalClose={onModalClose}>
|
||||||
|
<ManageCustomFormatsEditModalContent
|
||||||
|
customFormatIds={customFormatIds}
|
||||||
|
onSavePress={onSavePress}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ManageCustomFormatsEditModal;
|
@ -0,0 +1,16 @@
|
|||||||
|
.modalFooter {
|
||||||
|
composes: modalFooter from '~Components/Modal/ModalFooter.css';
|
||||||
|
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: $breakpointExtraSmall) {
|
||||||
|
.modalFooter {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
// This file is automatically generated.
|
||||||
|
// Please do not change this file!
|
||||||
|
interface CssExports {
|
||||||
|
'modalFooter': string;
|
||||||
|
'selected': string;
|
||||||
|
}
|
||||||
|
export const cssExports: CssExports;
|
||||||
|
export default cssExports;
|
@ -0,0 +1,125 @@
|
|||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
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 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 } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import styles from './ManageCustomFormatsEditModalContent.css';
|
||||||
|
|
||||||
|
interface SavePayload {
|
||||||
|
includeCustomFormatWhenRenaming?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ManageCustomFormatsEditModalContentProps {
|
||||||
|
customFormatIds: number[];
|
||||||
|
onSavePress(payload: object): void;
|
||||||
|
onModalClose(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NO_CHANGE = 'noChange';
|
||||||
|
|
||||||
|
const enableOptions = [
|
||||||
|
{
|
||||||
|
key: NO_CHANGE,
|
||||||
|
get value() {
|
||||||
|
return translate('NoChange');
|
||||||
|
},
|
||||||
|
isDisabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'enabled',
|
||||||
|
get value() {
|
||||||
|
return translate('Enabled');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'disabled',
|
||||||
|
get value() {
|
||||||
|
return translate('Disabled');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function ManageCustomFormatsEditModalContent(
|
||||||
|
props: ManageCustomFormatsEditModalContentProps
|
||||||
|
) {
|
||||||
|
const { customFormatIds, onSavePress, onModalClose } = props;
|
||||||
|
|
||||||
|
const [includeCustomFormatWhenRenaming, setIncludeCustomFormatWhenRenaming] =
|
||||||
|
useState(NO_CHANGE);
|
||||||
|
|
||||||
|
const save = useCallback(() => {
|
||||||
|
let hasChanges = false;
|
||||||
|
const payload: SavePayload = {};
|
||||||
|
|
||||||
|
if (includeCustomFormatWhenRenaming !== NO_CHANGE) {
|
||||||
|
hasChanges = true;
|
||||||
|
payload.includeCustomFormatWhenRenaming =
|
||||||
|
includeCustomFormatWhenRenaming === 'enabled';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasChanges) {
|
||||||
|
onSavePress(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
onModalClose();
|
||||||
|
}, [includeCustomFormatWhenRenaming, onSavePress, onModalClose]);
|
||||||
|
|
||||||
|
const onInputChange = useCallback(
|
||||||
|
({ name, value }: { name: string; value: string }) => {
|
||||||
|
switch (name) {
|
||||||
|
case 'includeCustomFormatWhenRenaming':
|
||||||
|
setIncludeCustomFormatWhenRenaming(value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn(
|
||||||
|
`EditCustomFormatsModalContent Unknown Input: '${name}'`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectedCount = customFormatIds.length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContent onModalClose={onModalClose}>
|
||||||
|
<ModalHeader>{translate('EditSelectedCustomFormats')}</ModalHeader>
|
||||||
|
|
||||||
|
<ModalBody>
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('IncludeCustomFormatWhenRenaming')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.SELECT}
|
||||||
|
name="includeCustomFormatWhenRenaming"
|
||||||
|
value={includeCustomFormatWhenRenaming}
|
||||||
|
values={enableOptions}
|
||||||
|
onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter className={styles.modalFooter}>
|
||||||
|
<div className={styles.selected}>
|
||||||
|
{translate('CountCustomFormatsSelected', {
|
||||||
|
count: selectedCount,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
|
||||||
|
|
||||||
|
<Button onPress={save}>{translate('ApplyChanges')}</Button>
|
||||||
|
</div>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ManageCustomFormatsEditModalContent;
|
@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Modal from 'Components/Modal/Modal';
|
||||||
|
import ManageCustomFormatsModalContent from './ManageCustomFormatsModalContent';
|
||||||
|
|
||||||
|
interface ManageCustomFormatsModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onModalClose(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ManageCustomFormatsModal(props: ManageCustomFormatsModalProps) {
|
||||||
|
const { isOpen, onModalClose } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={isOpen} onModalClose={onModalClose}>
|
||||||
|
<ManageCustomFormatsModalContent onModalClose={onModalClose} />
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ManageCustomFormatsModal;
|
@ -0,0 +1,16 @@
|
|||||||
|
.leftButtons,
|
||||||
|
.rightButtons {
|
||||||
|
display: flex;
|
||||||
|
flex: 1 0 50%;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rightButtons {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleteButton {
|
||||||
|
composes: button from '~Components/Link/Button.css';
|
||||||
|
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
// This file is automatically generated.
|
||||||
|
// Please do not change this file!
|
||||||
|
interface CssExports {
|
||||||
|
'deleteButton': string;
|
||||||
|
'leftButtons': string;
|
||||||
|
'rightButtons': string;
|
||||||
|
}
|
||||||
|
export const cssExports: CssExports;
|
||||||
|
export default cssExports;
|
@ -0,0 +1,241 @@
|
|||||||
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { CustomFormatAppState } 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';
|
||||||
|
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||||
|
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 Table from 'Components/Table/Table';
|
||||||
|
import TableBody from 'Components/Table/TableBody';
|
||||||
|
import useSelectState from 'Helpers/Hooks/useSelectState';
|
||||||
|
import { kinds } from 'Helpers/Props';
|
||||||
|
import SortDirection from 'Helpers/Props/SortDirection';
|
||||||
|
import {
|
||||||
|
bulkDeleteCustomFormats,
|
||||||
|
bulkEditCustomFormats,
|
||||||
|
setManageCustomFormatsSort,
|
||||||
|
} from 'Store/Actions/settingsActions';
|
||||||
|
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 ManageCustomFormatsEditModal from './Edit/ManageCustomFormatsEditModal';
|
||||||
|
import ManageCustomFormatsModalRow from './ManageCustomFormatsModalRow';
|
||||||
|
import styles from './ManageCustomFormatsModalContent.css';
|
||||||
|
|
||||||
|
// TODO: This feels janky to do, but not sure of a better way currently
|
||||||
|
type OnSelectedChangeCallback = React.ComponentProps<
|
||||||
|
typeof ManageCustomFormatsModalRow
|
||||||
|
>['onSelectedChange'];
|
||||||
|
|
||||||
|
const COLUMNS = [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
label: () => translate('Name'),
|
||||||
|
isSortable: true,
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'includeCustomFormatWhenRenaming',
|
||||||
|
label: () => translate('IncludeCustomFormatWhenRenaming'),
|
||||||
|
isSortable: true,
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
interface ManageCustomFormatsModalContentProps {
|
||||||
|
onModalClose(): void;
|
||||||
|
sortKey?: string;
|
||||||
|
sortDirection?: SortDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ManageCustomFormatsModalContent(
|
||||||
|
props: ManageCustomFormatsModalContentProps
|
||||||
|
) {
|
||||||
|
const { onModalClose } = props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
isDeleting,
|
||||||
|
isSaving,
|
||||||
|
error,
|
||||||
|
items,
|
||||||
|
sortKey,
|
||||||
|
sortDirection,
|
||||||
|
}: CustomFormatAppState = useSelector(
|
||||||
|
createClientSideCollectionSelector('settings.customFormats')
|
||||||
|
);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
|
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
||||||
|
|
||||||
|
const [selectState, setSelectState] = useSelectState();
|
||||||
|
|
||||||
|
const { allSelected, allUnselected, selectedState } = selectState;
|
||||||
|
|
||||||
|
const selectedIds: number[] = useMemo(() => {
|
||||||
|
return getSelectedIds(selectedState);
|
||||||
|
}, [selectedState]);
|
||||||
|
|
||||||
|
const selectedCount = selectedIds.length;
|
||||||
|
|
||||||
|
const onSortPress = useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
dispatch(setManageCustomFormatsSort({ sortKey: value }));
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onDeletePress = useCallback(() => {
|
||||||
|
setIsDeleteModalOpen(true);
|
||||||
|
}, [setIsDeleteModalOpen]);
|
||||||
|
|
||||||
|
const onDeleteModalClose = useCallback(() => {
|
||||||
|
setIsDeleteModalOpen(false);
|
||||||
|
}, [setIsDeleteModalOpen]);
|
||||||
|
|
||||||
|
const onEditPress = useCallback(() => {
|
||||||
|
setIsEditModalOpen(true);
|
||||||
|
}, [setIsEditModalOpen]);
|
||||||
|
|
||||||
|
const onEditModalClose = useCallback(() => {
|
||||||
|
setIsEditModalOpen(false);
|
||||||
|
}, [setIsEditModalOpen]);
|
||||||
|
|
||||||
|
const onConfirmDelete = useCallback(() => {
|
||||||
|
dispatch(bulkDeleteCustomFormats({ ids: selectedIds }));
|
||||||
|
setIsDeleteModalOpen(false);
|
||||||
|
}, [selectedIds, dispatch]);
|
||||||
|
|
||||||
|
const onSavePress = useCallback(
|
||||||
|
(payload: object) => {
|
||||||
|
setIsEditModalOpen(false);
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
bulkEditCustomFormats({
|
||||||
|
ids: selectedIds,
|
||||||
|
...payload,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[selectedIds, dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onSelectAllChange = useCallback(
|
||||||
|
({ value }: SelectStateInputProps) => {
|
||||||
|
setSelectState({ type: value ? 'selectAll' : 'unselectAll', items });
|
||||||
|
},
|
||||||
|
[items, setSelectState]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onSelectedChange = useCallback<OnSelectedChangeCallback>(
|
||||||
|
({ id, value, shiftKey = false }) => {
|
||||||
|
setSelectState({
|
||||||
|
type: 'toggleSelected',
|
||||||
|
items,
|
||||||
|
id,
|
||||||
|
isSelected: value,
|
||||||
|
shiftKey,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[items, setSelectState]
|
||||||
|
);
|
||||||
|
|
||||||
|
const errorMessage = getErrorMessage(error, 'Unable to load custom formats.');
|
||||||
|
const anySelected = selectedCount > 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContent onModalClose={onModalClose}>
|
||||||
|
<ModalHeader>{translate('ManageCustomFormats')}</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
{isFetching ? <LoadingIndicator /> : null}
|
||||||
|
|
||||||
|
{error ? <div>{errorMessage}</div> : null}
|
||||||
|
|
||||||
|
{isPopulated && !error && !items.length && (
|
||||||
|
<Alert kind={kinds.INFO}>{translate('NoCustomFormatsFound')}</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isPopulated && !!items.length && !isFetching && !isFetching ? (
|
||||||
|
<Table
|
||||||
|
columns={COLUMNS}
|
||||||
|
horizontalScroll={true}
|
||||||
|
selectAll={true}
|
||||||
|
allSelected={allSelected}
|
||||||
|
allUnselected={allUnselected}
|
||||||
|
sortKey={sortKey}
|
||||||
|
sortDirection={sortDirection}
|
||||||
|
onSelectAllChange={onSelectAllChange}
|
||||||
|
onSortPress={onSortPress}
|
||||||
|
>
|
||||||
|
<TableBody>
|
||||||
|
{items.map((item) => {
|
||||||
|
return (
|
||||||
|
<ManageCustomFormatsModalRow
|
||||||
|
key={item.id}
|
||||||
|
isSelected={selectedState[item.id]}
|
||||||
|
{...item}
|
||||||
|
columns={COLUMNS}
|
||||||
|
onSelectedChange={onSelectedChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
) : null}
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<div className={styles.leftButtons}>
|
||||||
|
<SpinnerButton
|
||||||
|
kind={kinds.DANGER}
|
||||||
|
isSpinning={isDeleting}
|
||||||
|
isDisabled={!anySelected}
|
||||||
|
onPress={onDeletePress}
|
||||||
|
>
|
||||||
|
{translate('Delete')}
|
||||||
|
</SpinnerButton>
|
||||||
|
|
||||||
|
<SpinnerButton
|
||||||
|
isSpinning={isSaving}
|
||||||
|
isDisabled={!anySelected}
|
||||||
|
onPress={onEditPress}
|
||||||
|
>
|
||||||
|
{translate('Edit')}
|
||||||
|
</SpinnerButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button onPress={onModalClose}>{translate('Close')}</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
|
||||||
|
<ManageCustomFormatsEditModal
|
||||||
|
isOpen={isEditModalOpen}
|
||||||
|
customFormatIds={selectedIds}
|
||||||
|
onModalClose={onEditModalClose}
|
||||||
|
onSavePress={onSavePress}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ConfirmModal
|
||||||
|
isOpen={isDeleteModalOpen}
|
||||||
|
kind={kinds.DANGER}
|
||||||
|
title={translate('DeleteSelectedCustomFormats')}
|
||||||
|
message={translate('DeleteSelectedCustomFormatsMessageText', {
|
||||||
|
count: selectedIds.length,
|
||||||
|
})}
|
||||||
|
confirmLabel={translate('Delete')}
|
||||||
|
onConfirm={onConfirmDelete}
|
||||||
|
onCancel={onDeleteModalClose}
|
||||||
|
/>
|
||||||
|
</ModalContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ManageCustomFormatsModalContent;
|
@ -0,0 +1,6 @@
|
|||||||
|
.name,
|
||||||
|
.includeCustomFormatWhenRenaming {
|
||||||
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
// This file is automatically generated.
|
||||||
|
// Please do not change this file!
|
||||||
|
interface CssExports {
|
||||||
|
'includeCustomFormatWhenRenaming': string;
|
||||||
|
'name': string;
|
||||||
|
}
|
||||||
|
export const cssExports: CssExports;
|
||||||
|
export default cssExports;
|
@ -0,0 +1,54 @@
|
|||||||
|
import React, { useCallback } from 'react';
|
||||||
|
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 { SelectStateInputProps } from 'typings/props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import styles from './ManageCustomFormatsModalRow.css';
|
||||||
|
|
||||||
|
interface ManageCustomFormatsModalRowProps {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
includeCustomFormatWhenRenaming: boolean;
|
||||||
|
columns: Column[];
|
||||||
|
isSelected?: boolean;
|
||||||
|
onSelectedChange(result: SelectStateInputProps): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ManageCustomFormatsModalRow(props: ManageCustomFormatsModalRowProps) {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
isSelected,
|
||||||
|
name,
|
||||||
|
includeCustomFormatWhenRenaming,
|
||||||
|
onSelectedChange,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const onSelectedChangeWrapper = useCallback(
|
||||||
|
(result: SelectStateInputProps) => {
|
||||||
|
onSelectedChange({
|
||||||
|
...result,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[onSelectedChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow>
|
||||||
|
<TableSelectCell
|
||||||
|
id={id}
|
||||||
|
isSelected={isSelected}
|
||||||
|
onSelectedChange={onSelectedChangeWrapper}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TableRowCell className={styles.name}>{name}</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell className={styles.includeCustomFormatWhenRenaming}>
|
||||||
|
{includeCustomFormatWhenRenaming ? translate('Yes') : translate('No')}
|
||||||
|
</TableRowCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ManageCustomFormatsModalRow;
|
@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||||
|
import useModalOpenState from 'Helpers/Hooks/useModalOpenState';
|
||||||
|
import { icons } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import ManageCustomFormatsModal from './ManageCustomFormatsModal';
|
||||||
|
|
||||||
|
function ManageCustomFormatsToolbarButton() {
|
||||||
|
const [isManageModalOpen, openManageModal, closeManageModal] =
|
||||||
|
useModalOpenState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PageToolbarButton
|
||||||
|
label={translate('ManageCustomFormats')}
|
||||||
|
iconName={icons.MANAGE}
|
||||||
|
onPress={openManageModal}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ManageCustomFormatsModal
|
||||||
|
isOpen={isManageModalOpen}
|
||||||
|
onModalClose={closeManageModal}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ManageCustomFormatsToolbarButton;
|
@ -1,7 +1,12 @@
|
|||||||
import { createAction } from 'redux-actions';
|
import { createAction } from 'redux-actions';
|
||||||
|
import { sortDirections } from 'Helpers/Props';
|
||||||
|
import createBulkEditItemHandler from 'Store/Actions/Creators/createBulkEditItemHandler';
|
||||||
|
import createBulkRemoveItemHandler from 'Store/Actions/Creators/createBulkRemoveItemHandler';
|
||||||
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
|
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
|
||||||
import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHandler';
|
import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHandler';
|
||||||
import createSaveProviderHandler from 'Store/Actions/Creators/createSaveProviderHandler';
|
import createSaveProviderHandler from 'Store/Actions/Creators/createSaveProviderHandler';
|
||||||
|
import createSetClientSideCollectionSortReducer
|
||||||
|
from 'Store/Actions/Creators/Reducers/createSetClientSideCollectionSortReducer';
|
||||||
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
|
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
|
||||||
import { createThunk } from 'Store/thunks';
|
import { createThunk } from 'Store/thunks';
|
||||||
import getSectionState from 'Utilities/State/getSectionState';
|
import getSectionState from 'Utilities/State/getSectionState';
|
||||||
@ -22,6 +27,9 @@ export const SAVE_CUSTOM_FORMAT = 'settings/customFormats/saveCustomFormat';
|
|||||||
export const DELETE_CUSTOM_FORMAT = 'settings/customFormats/deleteCustomFormat';
|
export const DELETE_CUSTOM_FORMAT = 'settings/customFormats/deleteCustomFormat';
|
||||||
export const SET_CUSTOM_FORMAT_VALUE = 'settings/customFormats/setCustomFormatValue';
|
export const SET_CUSTOM_FORMAT_VALUE = 'settings/customFormats/setCustomFormatValue';
|
||||||
export const CLONE_CUSTOM_FORMAT = 'settings/customFormats/cloneCustomFormat';
|
export const CLONE_CUSTOM_FORMAT = 'settings/customFormats/cloneCustomFormat';
|
||||||
|
export const BULK_EDIT_CUSTOM_FORMATS = 'settings/downloadClients/bulkEditCustomFormats';
|
||||||
|
export const BULK_DELETE_CUSTOM_FORMATS = 'settings/downloadClients/bulkDeleteCustomFormats';
|
||||||
|
export const SET_MANAGE_CUSTOM_FORMATS_SORT = 'settings/downloadClients/setManageCustomFormatsSort';
|
||||||
|
|
||||||
//
|
//
|
||||||
// Action Creators
|
// Action Creators
|
||||||
@ -29,6 +37,9 @@ export const CLONE_CUSTOM_FORMAT = 'settings/customFormats/cloneCustomFormat';
|
|||||||
export const fetchCustomFormats = createThunk(FETCH_CUSTOM_FORMATS);
|
export const fetchCustomFormats = createThunk(FETCH_CUSTOM_FORMATS);
|
||||||
export const saveCustomFormat = createThunk(SAVE_CUSTOM_FORMAT);
|
export const saveCustomFormat = createThunk(SAVE_CUSTOM_FORMAT);
|
||||||
export const deleteCustomFormat = createThunk(DELETE_CUSTOM_FORMAT);
|
export const deleteCustomFormat = createThunk(DELETE_CUSTOM_FORMAT);
|
||||||
|
export const bulkEditCustomFormats = createThunk(BULK_EDIT_CUSTOM_FORMATS);
|
||||||
|
export const bulkDeleteCustomFormats = createThunk(BULK_DELETE_CUSTOM_FORMATS);
|
||||||
|
export const setManageCustomFormatsSort = createAction(SET_MANAGE_CUSTOM_FORMATS_SORT);
|
||||||
|
|
||||||
export const setCustomFormatValue = createAction(SET_CUSTOM_FORMAT_VALUE, (payload) => {
|
export const setCustomFormatValue = createAction(SET_CUSTOM_FORMAT_VALUE, (payload) => {
|
||||||
return {
|
return {
|
||||||
@ -48,20 +59,30 @@ export default {
|
|||||||
// State
|
// State
|
||||||
|
|
||||||
defaultState: {
|
defaultState: {
|
||||||
isSchemaFetching: false,
|
|
||||||
isSchemaPopulated: false,
|
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
isPopulated: false,
|
isPopulated: false,
|
||||||
|
error: null,
|
||||||
|
isSaving: false,
|
||||||
|
saveError: null,
|
||||||
|
isDeleting: false,
|
||||||
|
deleteError: null,
|
||||||
|
items: [],
|
||||||
|
pendingChanges: {},
|
||||||
|
|
||||||
|
isSchemaFetching: false,
|
||||||
|
isSchemaPopulated: false,
|
||||||
|
schemaError: null,
|
||||||
schema: {
|
schema: {
|
||||||
includeCustomFormatWhenRenaming: false
|
includeCustomFormatWhenRenaming: false
|
||||||
},
|
},
|
||||||
error: null,
|
|
||||||
isDeleting: false,
|
sortKey: 'name',
|
||||||
deleteError: null,
|
sortDirection: sortDirections.ASCENDING,
|
||||||
isSaving: false,
|
sortPredicates: {
|
||||||
saveError: null,
|
name: ({ name }) => {
|
||||||
items: [],
|
return name.toLocaleLowerCase();
|
||||||
pendingChanges: {}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -83,7 +104,10 @@ export default {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
createSaveProviderHandler(section, '/customformat')(getState, payload, dispatch);
|
createSaveProviderHandler(section, '/customformat')(getState, payload, dispatch);
|
||||||
}
|
},
|
||||||
|
|
||||||
|
[BULK_EDIT_CUSTOM_FORMATS]: createBulkEditItemHandler(section, '/customformat/bulk'),
|
||||||
|
[BULK_DELETE_CUSTOM_FORMATS]: createBulkRemoveItemHandler(section, '/customformat/bulk')
|
||||||
},
|
},
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -103,7 +127,9 @@ export default {
|
|||||||
newState.pendingChanges = pendingChanges;
|
newState.pendingChanges = pendingChanges;
|
||||||
|
|
||||||
return updateSectionState(state, section, newState);
|
return updateSectionState(state, section, newState);
|
||||||
}
|
},
|
||||||
|
|
||||||
|
[SET_MANAGE_CUSTOM_FORMATS_SORT]: createSetClientSideCollectionSortReducer(section)
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -96,8 +96,8 @@ export default {
|
|||||||
sortKey: 'name',
|
sortKey: 'name',
|
||||||
sortDirection: sortDirections.ASCENDING,
|
sortDirection: sortDirections.ASCENDING,
|
||||||
sortPredicates: {
|
sortPredicates: {
|
||||||
name: function(item) {
|
name: ({ name }) => {
|
||||||
return item.name.toLowerCase();
|
return name.toLocaleLowerCase();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -101,8 +101,8 @@ export default {
|
|||||||
sortKey: 'name',
|
sortKey: 'name',
|
||||||
sortDirection: sortDirections.ASCENDING,
|
sortDirection: sortDirections.ASCENDING,
|
||||||
sortPredicates: {
|
sortPredicates: {
|
||||||
name: function(item) {
|
name: ({ name }) => {
|
||||||
return item.name.toLowerCase();
|
return name.toLocaleLowerCase();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
|
import ModelBase from 'App/ModelBase';
|
||||||
|
|
||||||
export interface QualityProfileFormatItem {
|
export interface QualityProfileFormatItem {
|
||||||
format: number;
|
format: number;
|
||||||
name: string;
|
name: string;
|
||||||
score: number;
|
score: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CustomFormat {
|
interface CustomFormat extends ModelBase {
|
||||||
id: number;
|
|
||||||
name: string;
|
name: string;
|
||||||
|
includeCustomFormatWhenRenaming: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CustomFormat;
|
export default CustomFormat;
|
||||||
|
@ -9,10 +9,12 @@ namespace NzbDrone.Core.CustomFormats
|
|||||||
public interface ICustomFormatService
|
public interface ICustomFormatService
|
||||||
{
|
{
|
||||||
void Update(CustomFormat customFormat);
|
void Update(CustomFormat customFormat);
|
||||||
|
void Update(List<CustomFormat> customFormat);
|
||||||
CustomFormat Insert(CustomFormat customFormat);
|
CustomFormat Insert(CustomFormat customFormat);
|
||||||
List<CustomFormat> All();
|
List<CustomFormat> All();
|
||||||
CustomFormat GetById(int id);
|
CustomFormat GetById(int id);
|
||||||
void Delete(int id);
|
void Delete(int id);
|
||||||
|
void Delete(List<int> ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CustomFormatService : ICustomFormatService
|
public class CustomFormatService : ICustomFormatService
|
||||||
@ -51,6 +53,12 @@ public void Update(CustomFormat customFormat)
|
|||||||
_cache.Clear();
|
_cache.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Update(List<CustomFormat> customFormat)
|
||||||
|
{
|
||||||
|
_formatRepository.UpdateMany(customFormat);
|
||||||
|
_cache.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
public CustomFormat Insert(CustomFormat customFormat)
|
public CustomFormat Insert(CustomFormat customFormat)
|
||||||
{
|
{
|
||||||
// Add to DB then insert into profiles
|
// Add to DB then insert into profiles
|
||||||
@ -72,5 +80,20 @@ public void Delete(int id)
|
|||||||
_formatRepository.Delete(id);
|
_formatRepository.Delete(id);
|
||||||
_cache.Clear();
|
_cache.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Delete(List<int> ids)
|
||||||
|
{
|
||||||
|
foreach (var id in ids)
|
||||||
|
{
|
||||||
|
var format = _formatRepository.Get(id);
|
||||||
|
|
||||||
|
// Remove from profiles before removing from DB
|
||||||
|
_eventAggregator.PublishEvent(new CustomFormatDeletedEvent(format));
|
||||||
|
|
||||||
|
_formatRepository.Delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
_cache.Clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -240,6 +240,7 @@
|
|||||||
"CouldNotConnectSignalR": "Could not connect to SignalR, UI won't update",
|
"CouldNotConnectSignalR": "Could not connect to SignalR, UI won't update",
|
||||||
"CouldNotFindResults": "Couldn't find any results for '{term}'",
|
"CouldNotFindResults": "Couldn't find any results for '{term}'",
|
||||||
"CountCollectionsSelected": "{count} collection(s) selected",
|
"CountCollectionsSelected": "{count} collection(s) selected",
|
||||||
|
"CountCustomFormatsSelected": "{count} custom formats(s) selected",
|
||||||
"CountDownloadClientsSelected": "{count} download client(s) selected",
|
"CountDownloadClientsSelected": "{count} download client(s) selected",
|
||||||
"CountImportListsSelected": "{count} import list(s) selected",
|
"CountImportListsSelected": "{count} import list(s) selected",
|
||||||
"CountIndexersSelected": "{count} indexer(s) selected",
|
"CountIndexersSelected": "{count} indexer(s) selected",
|
||||||
@ -337,6 +338,8 @@
|
|||||||
"DeleteRootFolder": "Delete Root Folder",
|
"DeleteRootFolder": "Delete Root Folder",
|
||||||
"DeleteRootFolderMessageText": "Are you sure you want to delete the root folder '{path}'?",
|
"DeleteRootFolderMessageText": "Are you sure you want to delete the root folder '{path}'?",
|
||||||
"DeleteSelected": "Delete Selected",
|
"DeleteSelected": "Delete Selected",
|
||||||
|
"DeleteSelectedCustomFormats": "Delete Custom Format(s)",
|
||||||
|
"DeleteSelectedCustomFormatsMessageText": "Are you sure you want to delete {count} selected custom format(s)?",
|
||||||
"DeleteSelectedDownloadClients": "Delete Download Client(s)",
|
"DeleteSelectedDownloadClients": "Delete Download Client(s)",
|
||||||
"DeleteSelectedDownloadClientsMessageText": "Are you sure you want to delete {count} selected 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?",
|
"DeleteSelectedImportListExclusionsMessageText": "Are you sure you want to delete the selected import list exclusions?",
|
||||||
@ -562,6 +565,7 @@
|
|||||||
"EditReleaseProfile": "Edit Release Profile",
|
"EditReleaseProfile": "Edit Release Profile",
|
||||||
"EditRemotePathMapping": "Edit Remote Path Mapping",
|
"EditRemotePathMapping": "Edit Remote Path Mapping",
|
||||||
"EditRestriction": "Edit Restriction",
|
"EditRestriction": "Edit Restriction",
|
||||||
|
"EditSelectedCustomFormats": "Edit Selected Custom Formats",
|
||||||
"EditSelectedDownloadClients": "Edit Selected Download Clients",
|
"EditSelectedDownloadClients": "Edit Selected Download Clients",
|
||||||
"EditSelectedImportLists": "Edit Selected Import Lists",
|
"EditSelectedImportLists": "Edit Selected Import Lists",
|
||||||
"EditSelectedIndexers": "Edit Selected Indexers",
|
"EditSelectedIndexers": "Edit Selected Indexers",
|
||||||
@ -852,6 +856,7 @@
|
|||||||
"MIA": "MIA",
|
"MIA": "MIA",
|
||||||
"MaintenanceRelease": "Maintenance Release: bug fixes and other improvements. See Github Commit History for more details",
|
"MaintenanceRelease": "Maintenance Release: bug fixes and other improvements. See Github Commit History for more details",
|
||||||
"ManageClients": "Manage Clients",
|
"ManageClients": "Manage Clients",
|
||||||
|
"ManageCustomFormats": "Manage Custom Formats",
|
||||||
"ManageDownloadClients": "Manage Download Clients",
|
"ManageDownloadClients": "Manage Download Clients",
|
||||||
"ManageFiles": "Manage Files",
|
"ManageFiles": "Manage Files",
|
||||||
"ManageImportLists": "Manage Import Lists",
|
"ManageImportLists": "Manage Import Lists",
|
||||||
@ -1007,6 +1012,7 @@
|
|||||||
"NoChange": "No Change",
|
"NoChange": "No Change",
|
||||||
"NoChanges": "No Changes",
|
"NoChanges": "No Changes",
|
||||||
"NoCollections": "No collections found, to get started you'll want to add a new movie, or import some existing ones",
|
"NoCollections": "No collections found, to get started you'll want to add a new movie, or import some existing ones",
|
||||||
|
"NoCustomFormatsFound": "No custom formats found",
|
||||||
"NoDelay": "No Delay",
|
"NoDelay": "No Delay",
|
||||||
"NoDownloadClientsFound": "No download clients found",
|
"NoDownloadClientsFound": "No download clients found",
|
||||||
"NoEventsFound": "No events found",
|
"NoEventsFound": "No events found",
|
||||||
|
10
src/Radarr.Api.V3/CustomFormats/CustomFormatBulkResource.cs
Normal file
10
src/Radarr.Api.V3/CustomFormats/CustomFormatBulkResource.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Radarr.Api.V3.CustomFormats
|
||||||
|
{
|
||||||
|
public class CustomFormatBulkResource
|
||||||
|
{
|
||||||
|
public HashSet<int> Ids { get; set; } = new ();
|
||||||
|
public bool? IncludeCustomFormatWhenRenaming { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -46,6 +46,13 @@ protected override CustomFormatResource GetResourceById(int id)
|
|||||||
return _formatService.GetById(id).ToResource(true);
|
return _formatService.GetById(id).ToResource(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Produces("application/json")]
|
||||||
|
public List<CustomFormatResource> GetAll()
|
||||||
|
{
|
||||||
|
return _formatService.All().ToResource(true);
|
||||||
|
}
|
||||||
|
|
||||||
[RestPostById]
|
[RestPostById]
|
||||||
[Consumes("application/json")]
|
[Consumes("application/json")]
|
||||||
public ActionResult<CustomFormatResource> Create([FromBody] CustomFormatResource customFormatResource)
|
public ActionResult<CustomFormatResource> Create([FromBody] CustomFormatResource customFormatResource)
|
||||||
@ -70,11 +77,26 @@ public ActionResult<CustomFormatResource> Update([FromBody] CustomFormatResource
|
|||||||
return Accepted(model.Id);
|
return Accepted(model.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpPut("bulk")]
|
||||||
|
[Consumes("application/json")]
|
||||||
[Produces("application/json")]
|
[Produces("application/json")]
|
||||||
public List<CustomFormatResource> GetAll()
|
public virtual ActionResult<CustomFormatResource> Update([FromBody] CustomFormatBulkResource resource)
|
||||||
{
|
{
|
||||||
return _formatService.All().ToResource(true);
|
if (!resource.Ids.Any())
|
||||||
|
{
|
||||||
|
throw new BadRequestException("ids must be provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
var customFormats = resource.Ids.Select(id => _formatService.GetById(id)).ToList();
|
||||||
|
|
||||||
|
customFormats.ForEach(existing =>
|
||||||
|
{
|
||||||
|
existing.IncludeCustomFormatWhenRenaming = resource.IncludeCustomFormatWhenRenaming ?? existing.IncludeCustomFormatWhenRenaming;
|
||||||
|
});
|
||||||
|
|
||||||
|
_formatService.Update(customFormats);
|
||||||
|
|
||||||
|
return Accepted(customFormats.ConvertAll(cf => cf.ToResource(true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
[RestDeleteById]
|
[RestDeleteById]
|
||||||
@ -83,12 +105,21 @@ public void DeleteFormat(int id)
|
|||||||
_formatService.Delete(id);
|
_formatService.Delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpDelete("bulk")]
|
||||||
|
[Consumes("application/json")]
|
||||||
|
public virtual object DeleteFormats([FromBody] CustomFormatBulkResource resource)
|
||||||
|
{
|
||||||
|
_formatService.Delete(resource.Ids.ToList());
|
||||||
|
|
||||||
|
return new { };
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet("schema")]
|
[HttpGet("schema")]
|
||||||
public object GetTemplates()
|
public object GetTemplates()
|
||||||
{
|
{
|
||||||
var schema = _specifications.OrderBy(x => x.Order).Select(x => x.ToSchema()).ToList();
|
var schema = _specifications.OrderBy(x => x.Order).Select(x => x.ToSchema()).ToList();
|
||||||
|
|
||||||
var presets = GetPresets();
|
var presets = GetPresets().ToList();
|
||||||
|
|
||||||
foreach (var item in schema)
|
foreach (var item in schema)
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user