mirror of https://github.com/Sonarr/Sonarr
296 lines
8.1 KiB
TypeScript
296 lines
8.1 KiB
TypeScript
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';
|
|
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 {
|
|
bulkDeleteIndexers,
|
|
bulkEditIndexers,
|
|
} 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 ManageIndexersEditModal from './Edit/ManageIndexersEditModal';
|
|
import ManageIndexersModalRow from './ManageIndexersModalRow';
|
|
import TagsModal from './Tags/TagsModal';
|
|
import styles from './ManageIndexersModalContent.css';
|
|
|
|
// TODO: This feels janky to do, but not sure of a better way currently
|
|
type OnSelectedChangeCallback = React.ComponentProps<
|
|
typeof ManageIndexersModalRow
|
|
>['onSelectedChange'];
|
|
|
|
const COLUMNS = [
|
|
{
|
|
name: 'name',
|
|
label: () => translate('Name'),
|
|
isSortable: true,
|
|
isVisible: true,
|
|
},
|
|
{
|
|
name: 'implementation',
|
|
label: () => translate('Implementation'),
|
|
isSortable: true,
|
|
isVisible: true,
|
|
},
|
|
{
|
|
name: 'enableRss',
|
|
label: () => translate('EnableRss'),
|
|
isSortable: true,
|
|
isVisible: true,
|
|
},
|
|
{
|
|
name: 'enableAutomaticSearch',
|
|
label: () => translate('EnableAutomaticSearch'),
|
|
isSortable: true,
|
|
isVisible: true,
|
|
},
|
|
{
|
|
name: 'enableInteractiveSearch',
|
|
label: () => translate('EnableInteractiveSearch'),
|
|
isSortable: true,
|
|
isVisible: true,
|
|
},
|
|
{
|
|
name: 'priority',
|
|
label: () => translate('Priority'),
|
|
isSortable: true,
|
|
isVisible: true,
|
|
},
|
|
{
|
|
name: 'tags',
|
|
label: () => translate('Tags'),
|
|
isSortable: true,
|
|
isVisible: true,
|
|
},
|
|
];
|
|
|
|
interface ManageIndexersModalContentProps {
|
|
onModalClose(): void;
|
|
}
|
|
|
|
function ManageIndexersModalContent(props: ManageIndexersModalContentProps) {
|
|
const { onModalClose } = props;
|
|
|
|
const {
|
|
isFetching,
|
|
isPopulated,
|
|
isDeleting,
|
|
isSaving,
|
|
error,
|
|
items,
|
|
}: IndexerAppState = useSelector(
|
|
createClientSideCollectionSelector('settings.indexers')
|
|
);
|
|
const dispatch = useDispatch();
|
|
|
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
|
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
|
const [isTagsModalOpen, setIsTagsModalOpen] = useState(false);
|
|
const [isSavingTags, setIsSavingTags] = useState(false);
|
|
|
|
const [selectState, setSelectState] = useSelectState();
|
|
|
|
const { allSelected, allUnselected, selectedState } = selectState;
|
|
|
|
const selectedIds: number[] = useMemo(() => {
|
|
return getSelectedIds(selectedState);
|
|
}, [selectedState]);
|
|
|
|
const selectedCount = selectedIds.length;
|
|
|
|
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(bulkDeleteIndexers({ ids: selectedIds }));
|
|
setIsDeleteModalOpen(false);
|
|
}, [selectedIds, dispatch]);
|
|
|
|
const onSavePress = useCallback(
|
|
(payload: object) => {
|
|
setIsEditModalOpen(false);
|
|
|
|
dispatch(
|
|
bulkEditIndexers({
|
|
ids: selectedIds,
|
|
...payload,
|
|
})
|
|
);
|
|
},
|
|
[selectedIds, dispatch]
|
|
);
|
|
|
|
const onTagsPress = useCallback(() => {
|
|
setIsTagsModalOpen(true);
|
|
}, [setIsTagsModalOpen]);
|
|
|
|
const onTagsModalClose = useCallback(() => {
|
|
setIsTagsModalOpen(false);
|
|
}, [setIsTagsModalOpen]);
|
|
|
|
const onApplyTagsPress = useCallback(
|
|
(tags: number[], applyTags: string) => {
|
|
setIsSavingTags(true);
|
|
setIsTagsModalOpen(false);
|
|
|
|
dispatch(
|
|
bulkEditIndexers({
|
|
ids: selectedIds,
|
|
tags,
|
|
applyTags,
|
|
})
|
|
);
|
|
},
|
|
[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 indexers.');
|
|
const anySelected = selectedCount > 0;
|
|
|
|
return (
|
|
<ModalContent onModalClose={onModalClose}>
|
|
<ModalHeader>{translate('ManageIndexers')}</ModalHeader>
|
|
<ModalBody>
|
|
{isFetching ? <LoadingIndicator /> : null}
|
|
|
|
{error ? <div>{errorMessage}</div> : null}
|
|
|
|
{isPopulated && !error && !items.length && (
|
|
<Alert kind={kinds.INFO}>{translate('NoIndexersFound')}</Alert>
|
|
)}
|
|
|
|
{isPopulated && !!items.length && !isFetching && !isFetching ? (
|
|
<Table
|
|
columns={COLUMNS}
|
|
horizontalScroll={true}
|
|
selectAll={true}
|
|
allSelected={allSelected}
|
|
allUnselected={allUnselected}
|
|
onSelectAllChange={onSelectAllChange}
|
|
>
|
|
<TableBody>
|
|
{items.map((item) => {
|
|
return (
|
|
<ManageIndexersModalRow
|
|
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>
|
|
|
|
<SpinnerButton
|
|
isSpinning={isSaving && isSavingTags}
|
|
isDisabled={!anySelected}
|
|
onPress={onTagsPress}
|
|
>
|
|
{translate('SetTags')}
|
|
</SpinnerButton>
|
|
</div>
|
|
|
|
<Button onPress={onModalClose}>{translate('Close')}</Button>
|
|
</ModalFooter>
|
|
|
|
<ManageIndexersEditModal
|
|
isOpen={isEditModalOpen}
|
|
onModalClose={onEditModalClose}
|
|
onSavePress={onSavePress}
|
|
indexerIds={selectedIds}
|
|
/>
|
|
|
|
<TagsModal
|
|
isOpen={isTagsModalOpen}
|
|
ids={selectedIds}
|
|
onApplyTagsPress={onApplyTagsPress}
|
|
onModalClose={onTagsModalClose}
|
|
/>
|
|
|
|
<ConfirmModal
|
|
isOpen={isDeleteModalOpen}
|
|
kind={kinds.DANGER}
|
|
title={translate('DeleteSelectedIndexers')}
|
|
message={translate('DeleteSelectedIndexersMessageText', {
|
|
count: selectedIds.length,
|
|
})}
|
|
confirmLabel={translate('Delete')}
|
|
onConfirm={onConfirmDelete}
|
|
onCancel={onDeleteModalClose}
|
|
/>
|
|
</ModalContent>
|
|
);
|
|
}
|
|
|
|
export default ManageIndexersModalContent;
|