Improve indexer multiple select functionality

pull/1765/head
Bogdan 11 months ago
parent 94c91d4c3f
commit 2ce5618499

@ -0,0 +1,11 @@
import { useEffect, useRef } from 'react';
export default function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T>();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}

@ -148,15 +148,15 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
);
const jumpBarItems = useMemo(() => {
// Reset if not sorting by sortTitle
if (sortKey !== 'sortTitle') {
// Reset if not sorting by sortName
if (sortKey !== 'sortName') {
return {
order: [],
};
}
const characters = items.reduce((acc, item) => {
let char = item.sortTitle.charAt(0);
let char = item.sortName.charAt(0);
if (!isNaN(char)) {
char = '#';
@ -225,7 +225,7 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
label={
isSelectMode
? translate('StopSelecting')
: translate('SelectIndexer')
: translate('SelectIndexers')
}
iconName={isSelectMode ? icons.SERIES_ENDED : icons.CHECK}
isSelectMode={isSelectMode}

@ -9,6 +9,7 @@ import ModalHeader from 'Components/Modal/ModalHeader';
import { kinds } from 'Helpers/Props';
import { bulkDeleteIndexers } from 'Store/Actions/indexerIndexActions';
import createAllIndexersSelector from 'Store/Selectors/createAllIndexersSelector';
import translate from 'Utilities/String/translate';
import styles from './DeleteIndexerModalContent.css';
interface DeleteIndexerModalContentProps {
@ -19,16 +20,16 @@ interface DeleteIndexerModalContentProps {
function DeleteIndexerModalContent(props: DeleteIndexerModalContentProps) {
const { indexerIds, onModalClose } = props;
const allIndexer = useSelector(createAllIndexersSelector());
const allIndexers = useSelector(createAllIndexersSelector());
const dispatch = useDispatch();
const indexers = useMemo(() => {
const selectedIndexers = useMemo(() => {
const indexers = indexerIds.map((id) => {
return allIndexer.find((s) => s.id === id);
return allIndexers.find((s) => s.id === id);
});
return orderBy(indexers, ['sortTitle']);
}, [indexerIds, allIndexer]);
return orderBy(indexers, ['sortName']);
}, [indexerIds, allIndexers]);
const onDeleteIndexerConfirmed = useCallback(() => {
dispatch(
@ -42,17 +43,19 @@ function DeleteIndexerModalContent(props: DeleteIndexerModalContentProps) {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>Delete Selected Indexer</ModalHeader>
<ModalHeader>{translate('DeleteSelectedIndexers')}</ModalHeader>
<ModalBody>
<div className={styles.message}>
{`Are you sure you want to delete ${indexers.length} selected indexers?`}
{translate('DeleteSelectedIndexersMessageText', [
selectedIndexers.length,
])}
</div>
<ul>
{indexers.map((s) => {
{selectedIndexers.map((s) => {
return (
<li key={s.name}>
<li key={s.id}>
<span>{s.name}</span>
</li>
);
@ -61,10 +64,10 @@ function DeleteIndexerModalContent(props: DeleteIndexerModalContentProps) {
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>Cancel</Button>
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
<Button kind={kinds.DANGER} onPress={onDeleteIndexerConfirmed}>
Delete
{translate('Delete')}
</Button>
</ModalFooter>
</ModalContent>

@ -67,7 +67,7 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
setAppProfileId(value);
break;
default:
console.warn('EditIndexerModalContent Unknown Input');
console.warn(`EditIndexersModalContent Unknown Input: '${name}'`);
}
},
[setEnable]
@ -81,7 +81,7 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>{translate('Edit Selected Indexer')}</ModalHeader>
<ModalHeader>{translate('EditSelectedIndexers')}</ModalHeader>
<ModalBody>
<FormGroup>
@ -112,14 +112,14 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
<ModalFooter className={styles.modalFooter}>
<div className={styles.selected}>
{translate('{0} indexers selected', selectedCount.toString())}
{translate('CountIndexersSelected', [selectedCount])}
</div>
<div>
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
<Button onPress={onSavePressWrapper}>
{translate('Apply Changes')}
{translate('ApplyChanges')}
</Button>
</div>
</ModalFooter>

@ -4,6 +4,7 @@ import { createSelector } from 'reselect';
import { useSelect } from 'App/SelectContext';
import SpinnerButton from 'Components/Link/SpinnerButton';
import PageContentFooter from 'Components/Page/PageContentFooter';
import usePrevious from 'Helpers/Hooks/usePrevious';
import { kinds } from 'Helpers/Props';
import { saveIndexerEditor } from 'Store/Actions/indexerIndexActions';
import translate from 'Utilities/String/translate';
@ -13,7 +14,7 @@ import EditIndexerModal from './Edit/EditIndexerModal';
import TagsModal from './Tags/TagsModal';
import styles from './IndexerIndexSelectFooter.css';
const seriesEditorSelector = createSelector(
const indexersEditorSelector = createSelector(
(state) => state.indexers,
(indexers) => {
const { isSaving, isDeleting, deleteError } = indexers;
@ -27,8 +28,9 @@ const seriesEditorSelector = createSelector(
);
function IndexerIndexSelectFooter() {
const { isSaving, isDeleting, deleteError } =
useSelector(seriesEditorSelector);
const { isSaving, isDeleting, deleteError } = useSelector(
indexersEditorSelector
);
const dispatch = useDispatch();
@ -37,6 +39,7 @@ function IndexerIndexSelectFooter() {
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [isSavingIndexer, setIsSavingIndexer] = useState(false);
const [isSavingTags, setIsSavingTags] = useState(false);
const previousIsDeleting = usePrevious(isDeleting);
const [selectState, selectDispatch] = useSelect();
const { selectedState } = selectState;
@ -110,10 +113,10 @@ function IndexerIndexSelectFooter() {
}, [isSaving]);
useEffect(() => {
if (!isDeleting && !deleteError) {
if (previousIsDeleting && !isDeleting && !deleteError) {
selectDispatch({ type: 'unselectAll' });
}
}, [isDeleting, deleteError, selectDispatch]);
}, [previousIsDeleting, isDeleting, deleteError, selectDispatch]);
const anySelected = selectedCount > 0;
@ -134,7 +137,7 @@ function IndexerIndexSelectFooter() {
isDisabled={!anySelected}
onPress={onTagsPress}
>
{translate('Set Tags')}
{translate('SetTags')}
</SpinnerButton>
</div>
@ -151,7 +154,7 @@ function IndexerIndexSelectFooter() {
</div>
<div className={styles.selected}>
{translate('{0} indexers selected', selectedCount.toString())}
{translate('CountIndexersSelected', [selectedCount])}
</div>
<EditIndexerModal

@ -59,14 +59,14 @@ 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>
@ -119,8 +119,8 @@ function TagsModalContent(props: TagsModalContentProps) {
key={tag.id}
title={
removeTag
? translate('RemoveTagRemovingTag')
: translate('RemoveTagExistingTag')
? translate('RemovingTag')
: translate('ExistingTag')
}
kind={removeTag ? kinds.INVERSE : kinds.INFO}
size={sizes.LARGE}
@ -159,10 +159,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>

@ -29,6 +29,8 @@ export const defaultState = {
isFetching: false,
isPopulated: false,
error: null,
isDeleting: false,
deleteError: null,
selectedSchema: {},
isSaving: false,
saveError: null,

@ -14,7 +14,7 @@ function createUnoptimizedSelector(uiSection) {
return {
id,
sortTitle: name
sortName: name
};
});
@ -38,7 +38,7 @@ const createMovieEqualSelector = createSelectorCreator(
function createIndexerClientSideCollectionItemsSelector(uiSection) {
return createMovieEqualSelector(
createUnoptimizedSelector(uiSection),
(movies) => movies
(indexers) => indexers
);
}

@ -41,6 +41,7 @@
"ApplicationUrlHelpText": "This application's external URL including http(s)://, port and URL base",
"Applications": "Applications",
"Apply": "Apply",
"ApplyChanges": "Apply Changes",
"ApplyTags": "Apply Tags",
"ApplyTagsHelpTexts1": "How to apply tags to the selected indexers",
"ApplyTagsHelpTexts2": "Add: Add the tags the existing list of tags",
@ -101,6 +102,7 @@
"ConnectionLostMessage": "Prowlarr has lost its connection to the backend and will need to be reloaded to restore functionality.",
"Connections": "Connections",
"CouldNotConnectSignalR": "Could not connect to SignalR, UI won't update",
"CountIndexersSelected": "{0} indexers selected",
"Custom": "Custom",
"CustomFilters": "Custom Filters",
"DBMigration": "DB Migration",
@ -120,6 +122,9 @@
"DeleteIndexerProxyMessageText": "Are you sure you want to delete the proxy '{0}'?",
"DeleteNotification": "Delete Notification",
"DeleteNotificationMessageText": "Are you sure you want to delete the notification '{0}'?",
"DeleteSelectedIndexer": "Delete Selected Indexer",
"DeleteSelectedIndexers": "Delete Selected Indexers",
"DeleteSelectedIndexersMessageText": "Are you sure you want to delete {0} selected indexer(s)?",
"DeleteTag": "Delete Tag",
"DeleteTagMessageText": "Are you sure you want to delete the tag '{0}'?",
"Description": "Description",
@ -140,6 +145,7 @@
"Duration": "Duration",
"Edit": "Edit",
"EditIndexer": "Edit Indexer",
"EditSelectedIndexers": "Edit Selected Indexers",
"EditSyncProfile": "Edit Sync Profile",
"ElapsedTime": "Elapsed Time",
"Enable": "Enable",
@ -391,7 +397,7 @@
"Security": "Security",
"Seeders": "Seeders",
"SelectAll": "Select All",
"SelectIndexer": "Select Indexer",
"SelectIndexers": "Select Indexers",
"SemiPrivate": "Semi-Private",
"SendAnonymousUsageData": "Send Anonymous Usage Data",
"SetTags": "Set Tags",

Loading…
Cancel
Save