diff --git a/frontend/src/Content/Fonts/fonts.css b/frontend/src/Content/Fonts/fonts.css
index e0f1bf5dc..bf31501dd 100644
--- a/frontend/src/Content/Fonts/fonts.css
+++ b/frontend/src/Content/Fonts/fonts.css
@@ -25,3 +25,14 @@
font-family: 'Ubuntu Mono';
src: url('UbuntuMono-Regular.eot?#iefix&v=1.3.0') format('embedded-opentype'), url('UbuntuMono-Regular.woff?v=1.3.0') format('woff'), url('UbuntuMono-Regular.ttf?v=1.3.0') format('truetype');
}
+
+/*
+ * text-security-disc
+ */
+
+@font-face {
+ font-weight: normal;
+ font-style: normal;
+ font-family: 'text-security-disc';
+ src: url('text-security-disc.woff?v=1.3.0') format('woff'), url('text-security-disc.ttf?v=1.3.0') format('truetype');
+}
diff --git a/frontend/src/Content/Fonts/text-security-disc.ttf b/frontend/src/Content/Fonts/text-security-disc.ttf
new file mode 100644
index 000000000..86038dba8
Binary files /dev/null and b/frontend/src/Content/Fonts/text-security-disc.ttf differ
diff --git a/frontend/src/Content/Fonts/text-security-disc.woff b/frontend/src/Content/Fonts/text-security-disc.woff
new file mode 100644
index 000000000..bc4cc324b
Binary files /dev/null and b/frontend/src/Content/Fonts/text-security-disc.woff differ
diff --git a/frontend/src/Content/Images/Icons/manifest.json b/frontend/src/Content/Images/Icons/manifest.json
index f53279dd3..abb5b4647 100644
--- a/frontend/src/Content/Images/Icons/manifest.json
+++ b/frontend/src/Content/Images/Icons/manifest.json
@@ -15,5 +15,5 @@
"start_url": "../../../../",
"theme_color": "#3a3f51",
"background_color": "#3a3f51",
- "display": "standalone"
+ "display": "minimal-ui"
}
diff --git a/frontend/src/DownloadClient/DownloadProtocol.ts b/frontend/src/DownloadClient/DownloadProtocol.ts
deleted file mode 100644
index 417db8178..000000000
--- a/frontend/src/DownloadClient/DownloadProtocol.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-type DownloadProtocol = 'usenet' | 'torrent' | 'unknown';
-
-export default DownloadProtocol;
diff --git a/frontend/src/Helpers/Hooks/useCurrentPage.ts b/frontend/src/Helpers/Hooks/useCurrentPage.ts
deleted file mode 100644
index 3caf66df2..000000000
--- a/frontend/src/Helpers/Hooks/useCurrentPage.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { useHistory } from 'react-router-dom';
-
-function useCurrentPage() {
- const history = useHistory();
-
- return history.action === 'POP';
-}
-
-export default useCurrentPage;
diff --git a/frontend/src/Helpers/Hooks/useModalOpenState.ts b/frontend/src/Helpers/Hooks/useModalOpenState.ts
deleted file mode 100644
index 24cffb2f1..000000000
--- a/frontend/src/Helpers/Hooks/useModalOpenState.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { useCallback, useState } from 'react';
-
-export default function useModalOpenState(
- initialState: boolean
-): [boolean, () => void, () => void] {
- const [isOpen, setIsOpen] = useState(initialState);
-
- const setModalOpen = useCallback(() => {
- setIsOpen(true);
- }, [setIsOpen]);
-
- const setModalClosed = useCallback(() => {
- setIsOpen(false);
- }, [setIsOpen]);
-
- return [isOpen, setModalOpen, setModalClosed];
-}
diff --git a/frontend/src/Helpers/Props/TooltipPosition.ts b/frontend/src/Helpers/Props/TooltipPosition.ts
deleted file mode 100644
index 885c73470..000000000
--- a/frontend/src/Helpers/Props/TooltipPosition.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-type TooltipPosition = 'top' | 'right' | 'bottom' | 'left';
-
-export default TooltipPosition;
diff --git a/frontend/src/Helpers/Props/align.ts b/frontend/src/Helpers/Props/align.js
similarity index 100%
rename from frontend/src/Helpers/Props/align.ts
rename to frontend/src/Helpers/Props/align.js
diff --git a/frontend/src/Helpers/Props/filterBuilderValueTypes.js b/frontend/src/Helpers/Props/filterBuilderValueTypes.js
index 73ef41956..19c8ccd9c 100644
--- a/frontend/src/Helpers/Props/filterBuilderValueTypes.js
+++ b/frontend/src/Helpers/Props/filterBuilderValueTypes.js
@@ -7,5 +7,5 @@ export const INDEXER = 'indexer';
export const PROTOCOL = 'protocol';
export const PRIVACY = 'privacy';
export const APP_PROFILE = 'appProfile';
-export const CATEGORY = 'category';
+export const MOVIE_STATUS = 'movieStatus';
export const TAG = 'tag';
diff --git a/frontend/src/Helpers/Props/icons.js b/frontend/src/Helpers/Props/icons.js
index 773748996..834452242 100644
--- a/frontend/src/Helpers/Props/icons.js
+++ b/frontend/src/Helpers/Props/icons.js
@@ -43,7 +43,6 @@ import {
faChevronCircleRight as fasChevronCircleRight,
faChevronCircleUp as fasChevronCircleUp,
faCircle as fasCircle,
- faCircleDown as fasCircleDown,
faCloud as fasCloud,
faCloudDownloadAlt as fasCloudDownloadAlt,
faCog as fasCog,
@@ -142,7 +141,6 @@ export const CHECK_INDETERMINATE = fasMinus;
export const CHECK_CIRCLE = fasCheckCircle;
export const CHECK_SQUARE = fasSquareCheck;
export const CIRCLE = fasCircle;
-export const CIRCLE_DOWN = fasCircleDown;
export const CIRCLE_OUTLINE = farCircle;
export const CLEAR = fasTrashAlt;
export const CLIPBOARD = fasCopy;
diff --git a/frontend/src/Helpers/Props/inputTypes.js b/frontend/src/Helpers/Props/inputTypes.js
index f9cd58e6d..6c4564341 100644
--- a/frontend/src/Helpers/Props/inputTypes.js
+++ b/frontend/src/Helpers/Props/inputTypes.js
@@ -1,5 +1,6 @@
export const AUTO_COMPLETE = 'autoComplete';
export const APP_PROFILE_SELECT = 'appProfileSelect';
+export const AVAILABILITY_SELECT = 'availabilitySelect';
export const CAPTCHA = 'captcha';
export const CARDIGANNCAPTCHA = 'cardigannCaptcha';
export const CHECK = 'check';
@@ -26,6 +27,7 @@ export const TAG_SELECT = 'tagSelect';
export const all = [
AUTO_COMPLETE,
APP_PROFILE_SELECT,
+ AVAILABILITY_SELECT,
CAPTCHA,
CARDIGANNCAPTCHA,
CHECK,
diff --git a/frontend/src/Helpers/Props/kinds.ts b/frontend/src/Helpers/Props/kinds.js
similarity index 72%
rename from frontend/src/Helpers/Props/kinds.ts
rename to frontend/src/Helpers/Props/kinds.js
index 7ce606716..b0f5ac87f 100644
--- a/frontend/src/Helpers/Props/kinds.ts
+++ b/frontend/src/Helpers/Props/kinds.js
@@ -7,6 +7,7 @@ export const PRIMARY = 'primary';
export const PURPLE = 'purple';
export const SUCCESS = 'success';
export const WARNING = 'warning';
+export const QUEUE = 'queue';
export const all = [
DANGER,
@@ -18,15 +19,5 @@ export const all = [
PURPLE,
SUCCESS,
WARNING,
-] as const;
-
-export type Kind =
- | 'danger'
- | 'default'
- | 'disabled'
- | 'info'
- | 'inverse'
- | 'primary'
- | 'purple'
- | 'success'
- | 'warning';
+ QUEUE
+];
diff --git a/frontend/src/Helpers/Props/sizes.ts b/frontend/src/Helpers/Props/sizes.js
similarity index 71%
rename from frontend/src/Helpers/Props/sizes.ts
rename to frontend/src/Helpers/Props/sizes.js
index ca7a50fbf..d7f85df5e 100644
--- a/frontend/src/Helpers/Props/sizes.ts
+++ b/frontend/src/Helpers/Props/sizes.js
@@ -4,6 +4,4 @@ export const MEDIUM = 'medium';
export const LARGE = 'large';
export const EXTRA_LARGE = 'extraLarge';
-export const all = [EXTRA_SMALL, SMALL, MEDIUM, LARGE, EXTRA_LARGE] as const;
-
-export type Size = 'extraSmall' | 'small' | 'medium' | 'large' | 'extraLarge';
+export const all = [EXTRA_SMALL, SMALL, MEDIUM, LARGE, EXTRA_LARGE];
diff --git a/frontend/src/History/Details/HistoryDetails.js b/frontend/src/History/Details/HistoryDetails.js
index 6d5ab260e..e0ae06eb1 100644
--- a/frontend/src/History/Details/HistoryDetails.js
+++ b/frontend/src/History/Details/HistoryDetails.js
@@ -3,7 +3,6 @@ import React from 'react';
import DescriptionList from 'Components/DescriptionList/DescriptionList';
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
import Link from 'Components/Link/Link';
-import formatDateTime from 'Utilities/Date/formatDateTime';
import translate from 'Utilities/String/translate';
import styles from './HistoryDetails.css';
@@ -11,10 +10,7 @@ function HistoryDetails(props) {
const {
indexer,
eventType,
- date,
- data,
- shortDateFormat,
- timeFormat
+ data
} = props;
if (eventType === 'indexerQuery' || eventType === 'indexerRss') {
@@ -25,10 +21,7 @@ function HistoryDetails(props) {
limit,
offset,
source,
- host,
- url,
- elapsedTime,
- cached
+ url
} = data;
return (
@@ -93,15 +86,6 @@ function HistoryDetails(props) {
null
}
- {
- data ?
-
:
- null
- }
-
{
data ?
:
null
}
-
- {
- elapsedTime ?
-
:
- null
- }
-
- {
- date ?
-
:
- null
- }
);
}
@@ -135,19 +101,10 @@ function HistoryDetails(props) {
if (eventType === 'releaseGrabbed') {
const {
source,
- host,
grabTitle,
- url,
- publishedDate,
- infoUrl,
- downloadClient,
- downloadClientName,
- elapsedTime,
- grabMethod
+ url
} = data;
- const downloadClientNameInfo = downloadClientName ?? downloadClient;
-
return (
{
@@ -168,15 +125,6 @@ function HistoryDetails(props) {
null
}
- {
- data ?
- :
- null
- }
-
{
data ?
{infoUrl}}
- /> :
- null
- }
-
- {
- publishedDate ?
- :
- null
- }
-
- {
- downloadClientNameInfo ?
- :
- null
- }
-
{
data ?
:
null
}
-
- {
- elapsedTime ?
- :
- null
- }
-
- {
- grabMethod ?
- :
- null
- }
-
- {
- date ?
- :
- null
- }
);
}
if (eventType === 'indexerAuth') {
- const { elapsedTime } = data;
-
return (
:
null
}
-
- {
- elapsedTime ?
-
:
- null
- }
-
- {
- date ?
-
:
- null
- }
);
}
@@ -297,15 +171,6 @@ function HistoryDetails(props) {
title={translate('Name')}
data={data.query}
/>
-
- {
- date ?
-
:
- null
- }
);
}
@@ -313,7 +178,6 @@ function HistoryDetails(props) {
HistoryDetails.propTypes = {
indexer: PropTypes.object.isRequired,
eventType: PropTypes.string.isRequired,
- date: PropTypes.string.isRequired,
data: PropTypes.object.isRequired,
shortDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired
diff --git a/frontend/src/History/Details/HistoryDetailsModal.js b/frontend/src/History/Details/HistoryDetailsModal.js
index 560955de3..fbc3114ad 100644
--- a/frontend/src/History/Details/HistoryDetailsModal.js
+++ b/frontend/src/History/Details/HistoryDetailsModal.js
@@ -29,7 +29,6 @@ function HistoryDetailsModal(props) {
isOpen,
eventType,
indexer,
- date,
data,
shortDateFormat,
timeFormat,
@@ -50,7 +49,6 @@ function HistoryDetailsModal(props) {
);
}
@@ -332,21 +331,6 @@ class HistoryRow extends Component {
);
}
- if (name === 'host') {
- return (
-
- {
- data.host ?
- data.host :
- null
- }
-
- );
- }
-
if (name === 'elapsedTime') {
return (
);
}
@@ -410,7 +393,6 @@ class HistoryRow extends Component {
{value};
} else if (type === 'tmdb') {
link = (
-
- {value}
-
+ {value}
);
} else if (type === 'tvdb') {
link = (
diff --git a/frontend/src/Indexer/Add/AddIndexerModal.js b/frontend/src/Indexer/Add/AddIndexerModal.js
new file mode 100644
index 000000000..9344c8130
--- /dev/null
+++ b/frontend/src/Indexer/Add/AddIndexerModal.js
@@ -0,0 +1,31 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import Modal from 'Components/Modal/Modal';
+import { sizes } from 'Helpers/Props';
+import AddIndexerModalContentConnector from './AddIndexerModalContentConnector';
+import styles from './AddIndexerModal.css';
+
+function AddIndexerModal({ isOpen, onModalClose, onSelectIndexer, ...otherProps }) {
+ return (
+
+
+
+ );
+}
+
+AddIndexerModal.propTypes = {
+ isOpen: PropTypes.bool.isRequired,
+ onModalClose: PropTypes.func.isRequired,
+ onSelectIndexer: PropTypes.func.isRequired
+};
+
+export default AddIndexerModal;
diff --git a/frontend/src/Indexer/Add/AddIndexerModal.tsx b/frontend/src/Indexer/Add/AddIndexerModal.tsx
deleted file mode 100644
index be22eec57..000000000
--- a/frontend/src/Indexer/Add/AddIndexerModal.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import React, { useCallback } from 'react';
-import { useDispatch } from 'react-redux';
-import Modal from 'Components/Modal/Modal';
-import { sizes } from 'Helpers/Props';
-import { clearIndexerSchema } from 'Store/Actions/indexerActions';
-import AddIndexerModalContent from './AddIndexerModalContent';
-import styles from './AddIndexerModal.css';
-
-interface AddIndexerModalProps {
- isOpen: boolean;
- onSelectIndexer(): void;
- onModalClose(): void;
-}
-
-function AddIndexerModal({
- isOpen,
- onSelectIndexer,
- onModalClose,
- ...otherProps
-}: AddIndexerModalProps) {
- const dispatch = useDispatch();
-
- const onModalClosePress = useCallback(() => {
- dispatch(clearIndexerSchema());
- onModalClose();
- }, [dispatch, onModalClose]);
-
- return (
-
-
-
- );
-}
-
-export default AddIndexerModal;
diff --git a/frontend/src/Indexer/Add/AddIndexerModalContent.css b/frontend/src/Indexer/Add/AddIndexerModalContent.css
index e824c5475..a58eccfbc 100644
--- a/frontend/src/Indexer/Add/AddIndexerModalContent.css
+++ b/frontend/src/Indexer/Add/AddIndexerModalContent.css
@@ -19,16 +19,10 @@
margin-bottom: 16px;
}
-.notice {
- composes: alert from '~Components/Alert.css';
-
- margin-bottom: 20px;
-}
-
.alert {
composes: alert from '~Components/Alert.css';
- text-align: center;
+ margin-bottom: 20px;
}
.scroller {
diff --git a/frontend/src/Indexer/Add/AddIndexerModalContent.css.d.ts b/frontend/src/Indexer/Add/AddIndexerModalContent.css.d.ts
index 5978832e4..cbedc72a4 100644
--- a/frontend/src/Indexer/Add/AddIndexerModalContent.css.d.ts
+++ b/frontend/src/Indexer/Add/AddIndexerModalContent.css.d.ts
@@ -10,7 +10,6 @@ interface CssExports {
'indexers': string;
'modalBody': string;
'modalFooter': string;
- 'notice': string;
'scroller': string;
}
export const cssExports: CssExports;
diff --git a/frontend/src/Indexer/Add/AddIndexerModalContent.js b/frontend/src/Indexer/Add/AddIndexerModalContent.js
new file mode 100644
index 000000000..f4050560e
--- /dev/null
+++ b/frontend/src/Indexer/Add/AddIndexerModalContent.js
@@ -0,0 +1,324 @@
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import Alert from 'Components/Alert';
+import EnhancedSelectInput from 'Components/Form/EnhancedSelectInput';
+import NewznabCategorySelectInputConnector from 'Components/Form/NewznabCategorySelectInputConnector';
+import TextInput from 'Components/Form/TextInput';
+import Button from 'Components/Link/Button';
+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 Scroller from 'Components/Scroller/Scroller';
+import Table from 'Components/Table/Table';
+import TableBody from 'Components/Table/TableBody';
+import { kinds, scrollDirections } from 'Helpers/Props';
+import getErrorMessage from 'Utilities/Object/getErrorMessage';
+import translate from 'Utilities/String/translate';
+import SelectIndexerRow from './SelectIndexerRow';
+import styles from './AddIndexerModalContent.css';
+
+const columns = [
+ {
+ name: 'protocol',
+ label: () => translate('Protocol'),
+ isSortable: true,
+ isVisible: true
+ },
+ {
+ name: 'sortName',
+ label: () => translate('Name'),
+ isSortable: true,
+ isVisible: true
+ },
+ {
+ name: 'language',
+ label: () => translate('Language'),
+ isSortable: true,
+ isVisible: true
+ },
+ {
+ name: 'description',
+ label: () => translate('Description'),
+ isSortable: false,
+ isVisible: true
+ },
+ {
+ name: 'privacy',
+ label: () => translate('Privacy'),
+ isSortable: true,
+ isVisible: true
+ },
+ {
+ name: 'categories',
+ label: () => translate('Categories'),
+ isSortable: false,
+ isVisible: true
+ }
+];
+
+const protocols = [
+ {
+ key: 'torrent',
+ value: 'torrent'
+ },
+ {
+ key: 'usenet',
+ value: 'nzb'
+ }
+];
+
+const privacyLevels = [
+ {
+ key: 'private',
+ get value() {
+ return translate('Private');
+ }
+ },
+ {
+ key: 'semiPrivate',
+ get value() {
+ return translate('SemiPrivate');
+ }
+ },
+ {
+ key: 'public',
+ get value() {
+ return translate('Public');
+ }
+ }
+];
+
+class AddIndexerModalContent extends Component {
+
+ //
+ // Lifecycle
+
+ constructor(props, context) {
+ super(props, context);
+
+ this.state = {
+ filter: '',
+ filterProtocols: [],
+ filterLanguages: [],
+ filterPrivacyLevels: [],
+ filterCategories: []
+ };
+ }
+
+ //
+ // Listeners
+
+ onFilterChange = ({ value }) => {
+ this.setState({ filter: value });
+ };
+
+ //
+ // Render
+
+ render() {
+ const {
+ indexers,
+ onIndexerSelect,
+ sortKey,
+ sortDirection,
+ isFetching,
+ isPopulated,
+ error,
+ onSortPress,
+ onModalClose
+ } = this.props;
+
+ const languages = Array.from(new Set(indexers.map(({ language }) => language)))
+ .sort((a, b) => a.localeCompare(b))
+ .map((language) => ({ key: language, value: language }));
+
+ const filteredIndexers = indexers.filter((indexer) => {
+ const {
+ filter,
+ filterProtocols,
+ filterLanguages,
+ filterPrivacyLevels,
+ filterCategories
+ } = this.state;
+
+ if (!indexer.name.toLowerCase().includes(filter.toLocaleLowerCase()) && !indexer.description.toLowerCase().includes(filter.toLocaleLowerCase())) {
+ return false;
+ }
+
+ if (filterProtocols.length && !filterProtocols.includes(indexer.protocol)) {
+ return false;
+ }
+
+ if (filterLanguages.length && !filterLanguages.includes(indexer.language)) {
+ return false;
+ }
+
+ if (filterPrivacyLevels.length && !filterPrivacyLevels.includes(indexer.privacy)) {
+ return false;
+ }
+
+ if (filterCategories.length) {
+ const { categories = [] } = indexer.capabilities || {};
+ const flat = ({ id, subCategories = [] }) => [id, ...subCategories.flatMap(flat)];
+ const flatCategories = categories
+ .filter((item) => item.id < 100000)
+ .flatMap(flat);
+
+ if (!filterCategories.every((item) => flatCategories.includes(item))) {
+ return false;
+ }
+ }
+
+ return true;
+ });
+
+ const errorMessage = getErrorMessage(error, translate('UnableToLoadIndexers'));
+
+ return (
+
+
+ {translate('AddIndexer')}
+
+
+
+
+
+
+
+
+ this.setState({ filterProtocols: value })}
+ />
+
+
+
+
+ this.setState({ filterLanguages: value })}
+ />
+
+
+
+
+ this.setState({ filterPrivacyLevels: value })}
+ />
+
+
+
+
+ this.setState({ filterCategories: value })}
+ />
+
+
+
+
+
+ {translate('ProwlarrSupportsAnyIndexer')}
+
+
+
+
+ {
+ isFetching ? : null
+ }
+ {
+ error ? {errorMessage} : null
+ }
+ {
+ isPopulated && !!indexers.length ?
+
+
+ {
+ filteredIndexers.map((indexer) => (
+
+ ))
+ }
+
+
:
+ null
+ }
+ {
+ isPopulated && !!indexers.length && !filteredIndexers.length ?
+
+ {translate('NoIndexersFound')}
+ :
+ null
+ }
+
+
+
+
+
+ {
+ isPopulated ?
+ translate('CountIndexersAvailable', { count: filteredIndexers.length }) :
+ null
+ }
+
+
+
+
+
+
+
+ );
+ }
+}
+
+AddIndexerModalContent.propTypes = {
+ isFetching: PropTypes.bool.isRequired,
+ isPopulated: PropTypes.bool.isRequired,
+ error: PropTypes.object,
+ sortKey: PropTypes.string,
+ sortDirection: PropTypes.string,
+ onSortPress: PropTypes.func.isRequired,
+ indexers: PropTypes.arrayOf(PropTypes.object).isRequired,
+ onIndexerSelect: PropTypes.func.isRequired,
+ onModalClose: PropTypes.func.isRequired
+};
+
+export default AddIndexerModalContent;
diff --git a/frontend/src/Indexer/Add/AddIndexerModalContent.tsx b/frontend/src/Indexer/Add/AddIndexerModalContent.tsx
deleted file mode 100644
index be1413769..000000000
--- a/frontend/src/Indexer/Add/AddIndexerModalContent.tsx
+++ /dev/null
@@ -1,434 +0,0 @@
-import { some } from 'lodash';
-import React, { useCallback, useEffect, useMemo, useState } from 'react';
-import { useDispatch, useSelector } from 'react-redux';
-import { createSelector } from 'reselect';
-import IndexerAppState from 'App/State/IndexerAppState';
-import Alert from 'Components/Alert';
-import EnhancedSelectInput from 'Components/Form/EnhancedSelectInput';
-import NewznabCategorySelectInputConnector from 'Components/Form/NewznabCategorySelectInputConnector';
-import TextInput from 'Components/Form/TextInput';
-import Button from 'Components/Link/Button';
-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 Scroller from 'Components/Scroller/Scroller';
-import Table from 'Components/Table/Table';
-import TableBody from 'Components/Table/TableBody';
-import { kinds, scrollDirections } from 'Helpers/Props';
-import Indexer, { IndexerCategory } from 'Indexer/Indexer';
-import {
- fetchIndexerSchema,
- selectIndexerSchema,
- setIndexerSchemaSort,
-} from 'Store/Actions/indexerActions';
-import createAllIndexersSelector from 'Store/Selectors/createAllIndexersSelector';
-import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
-import { SortCallback } from 'typings/callbacks';
-import sortByProp from 'Utilities/Array/sortByProp';
-import getErrorMessage from 'Utilities/Object/getErrorMessage';
-import translate from 'Utilities/String/translate';
-import SelectIndexerRow from './SelectIndexerRow';
-import styles from './AddIndexerModalContent.css';
-
-const COLUMNS = [
- {
- name: 'protocol',
- label: () => translate('Protocol'),
- isSortable: true,
- isVisible: true,
- },
- {
- name: 'sortName',
- label: () => translate('Name'),
- isSortable: true,
- isVisible: true,
- },
- {
- name: 'language',
- label: () => translate('Language'),
- isSortable: true,
- isVisible: true,
- },
- {
- name: 'description',
- label: () => translate('Description'),
- isSortable: false,
- isVisible: true,
- },
- {
- name: 'privacy',
- label: () => translate('Privacy'),
- isSortable: true,
- isVisible: true,
- },
- {
- name: 'categories',
- label: () => translate('Categories'),
- isSortable: false,
- isVisible: true,
- },
-];
-
-const PROTOCOLS = [
- {
- key: 'torrent',
- value: 'torrent',
- },
- {
- key: 'usenet',
- value: 'nzb',
- },
-];
-
-const PRIVACY_LEVELS = [
- {
- key: 'private',
- get value() {
- return translate('Private');
- },
- },
- {
- key: 'semiPrivate',
- get value() {
- return translate('SemiPrivate');
- },
- },
- {
- key: 'public',
- get value() {
- return translate('Public');
- },
- },
-];
-
-interface IndexerSchema extends Indexer {
- isExistingIndexer: boolean;
-}
-
-function createAddIndexersSelector() {
- return createSelector(
- createClientSideCollectionSelector('indexers.schema'),
- createAllIndexersSelector(),
- (indexers: IndexerAppState, allIndexers) => {
- const { isFetching, isPopulated, error, items, sortDirection, sortKey } =
- indexers;
-
- const indexerList: IndexerSchema[] = items.map((item) => {
- const { definitionName } = item;
- return {
- ...item,
- isExistingIndexer: some(allIndexers, { definitionName }),
- };
- });
-
- return {
- isFetching,
- isPopulated,
- error,
- indexers: indexerList,
- sortKey,
- sortDirection,
- };
- }
- );
-}
-
-interface AddIndexerModalContentProps {
- onSelectIndexer(): void;
- onModalClose(): void;
-}
-
-function AddIndexerModalContent(props: AddIndexerModalContentProps) {
- const { onSelectIndexer, onModalClose } = props;
-
- const { isFetching, isPopulated, error, indexers, sortKey, sortDirection } =
- useSelector(createAddIndexersSelector());
- const dispatch = useDispatch();
-
- const [filter, setFilter] = useState('');
- const [filterProtocols, setFilterProtocols] = useState([]);
- const [filterLanguages, setFilterLanguages] = useState([]);
- const [filterPrivacyLevels, setFilterPrivacyLevels] = useState([]);
- const [filterCategories, setFilterCategories] = useState([]);
-
- useEffect(
- () => {
- dispatch(fetchIndexerSchema());
- },
- // eslint-disable-next-line react-hooks/exhaustive-deps
- []
- );
-
- const onFilterChange = useCallback(
- ({ value }: { value: string }) => {
- setFilter(value);
- },
- [setFilter]
- );
-
- const onFilterProtocolsChange = useCallback(
- ({ value }: { value: string[] }) => {
- setFilterProtocols(value);
- },
- [setFilterProtocols]
- );
-
- const onFilterLanguagesChange = useCallback(
- ({ value }: { value: string[] }) => {
- setFilterLanguages(value);
- },
- [setFilterLanguages]
- );
-
- const onFilterPrivacyLevelsChange = useCallback(
- ({ value }: { value: string[] }) => {
- setFilterPrivacyLevels(value);
- },
- [setFilterPrivacyLevels]
- );
-
- const onFilterCategoriesChange = useCallback(
- ({ value }: { value: number[] }) => {
- setFilterCategories(value);
- },
- [setFilterCategories]
- );
-
- const onIndexerSelect = useCallback(
- ({
- implementation,
- implementationName,
- name,
- }: {
- implementation: string;
- implementationName: string;
- name: string;
- }) => {
- dispatch(
- selectIndexerSchema({
- implementation,
- implementationName,
- name,
- })
- );
-
- onSelectIndexer();
- },
- [dispatch, onSelectIndexer]
- );
-
- const onSortPress = useCallback(
- (sortKey, sortDirection) => {
- dispatch(setIndexerSchemaSort({ sortKey, sortDirection }));
- },
- [dispatch]
- );
-
- const languages = useMemo(
- () =>
- Array.from(new Set(indexers.map(({ language }) => language)))
- .map((language) => ({ key: language, value: language }))
- .sort(sortByProp('value')),
- [indexers]
- );
-
- const filteredIndexers = useMemo(() => {
- const flat = ({
- id,
- subCategories = [],
- }: {
- id: number;
- subCategories: IndexerCategory[];
- }): number[] => [id, ...subCategories.flatMap(flat)];
-
- return indexers.filter((indexer) => {
- if (
- filter.length &&
- !indexer.name.toLowerCase().includes(filter.toLocaleLowerCase()) &&
- !indexer.description.toLowerCase().includes(filter.toLocaleLowerCase())
- ) {
- return false;
- }
-
- if (
- filterProtocols.length &&
- !filterProtocols.includes(indexer.protocol)
- ) {
- return false;
- }
-
- if (
- filterLanguages.length &&
- !filterLanguages.includes(indexer.language)
- ) {
- return false;
- }
-
- if (
- filterPrivacyLevels.length &&
- !filterPrivacyLevels.includes(indexer.privacy)
- ) {
- return false;
- }
-
- if (filterCategories.length) {
- const { categories = [] } = indexer.capabilities || {};
-
- const flatCategories = categories
- .filter((item) => item.id < 100000)
- .flatMap(flat);
-
- if (
- !filterCategories.every((categoryId) =>
- flatCategories.includes(categoryId)
- )
- ) {
- return false;
- }
- }
-
- return true;
- });
- }, [
- indexers,
- filter,
- filterProtocols,
- filterLanguages,
- filterPrivacyLevels,
- filterCategories,
- ]);
-
- const errorMessage = getErrorMessage(
- error,
- translate('UnableToLoadIndexers')
- );
-
- return (
-
- {translate('AddIndexer')}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {translate('ProwlarrSupportsAnyIndexer')}
-
-
-
- {isFetching ? : null}
-
- {error ? (
-
- {errorMessage}
-
- ) : null}
-
- {isPopulated && !!indexers.length ? (
-
-
- {filteredIndexers.map((indexer) => (
-
- ))}
-
-
- ) : null}
-
- {isPopulated && !!indexers.length && !filteredIndexers.length ? (
-
- {translate('NoIndexersFound')}
-
- ) : null}
-
-
-
-
-
- {isPopulated
- ? translate('CountIndexersAvailable', {
- count: filteredIndexers.length,
- })
- : null}
-
-
-
-
-
-
-
- );
-}
-
-export default AddIndexerModalContent;
diff --git a/frontend/src/Indexer/Add/AddIndexerModalContentConnector.js b/frontend/src/Indexer/Add/AddIndexerModalContentConnector.js
new file mode 100644
index 000000000..a422e0a03
--- /dev/null
+++ b/frontend/src/Indexer/Add/AddIndexerModalContentConnector.js
@@ -0,0 +1,94 @@
+import { some } from 'lodash';
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import { createSelector } from 'reselect';
+import { fetchIndexerSchema, selectIndexerSchema, setIndexerSchemaSort } from 'Store/Actions/indexerActions';
+import createAllIndexersSelector from 'Store/Selectors/createAllIndexersSelector';
+import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
+import AddIndexerModalContent from './AddIndexerModalContent';
+
+function createMapStateToProps() {
+ return createSelector(
+ createClientSideCollectionSelector('indexers.schema'),
+ createAllIndexersSelector(),
+ (indexers, allIndexers) => {
+ const {
+ isFetching,
+ isPopulated,
+ error,
+ items,
+ sortDirection,
+ sortKey
+ } = indexers;
+
+ const indexerList = items.map((item) => {
+ const { definitionName } = item;
+ return {
+ ...item,
+ isExistingIndexer: some(allIndexers, { definitionName })
+ };
+ });
+
+ return {
+ isFetching,
+ isPopulated,
+ error,
+ indexers: indexerList,
+ sortKey,
+ sortDirection
+ };
+ }
+ );
+}
+
+const mapDispatchToProps = {
+ fetchIndexerSchema,
+ selectIndexerSchema,
+ setIndexerSchemaSort
+};
+
+class AddIndexerModalContentConnector extends Component {
+
+ //
+ // Lifecycle
+
+ componentDidMount() {
+ this.props.fetchIndexerSchema();
+ }
+
+ //
+ // Listeners
+
+ onIndexerSelect = ({ implementation, implementationName, name }) => {
+ this.props.selectIndexerSchema({ implementation, implementationName, name });
+ this.props.onSelectIndexer();
+ };
+
+ onSortPress = (sortKey, sortDirection) => {
+ this.props.setIndexerSchemaSort({ sortKey, sortDirection });
+ };
+
+ //
+ // Render
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+AddIndexerModalContentConnector.propTypes = {
+ fetchIndexerSchema: PropTypes.func.isRequired,
+ selectIndexerSchema: PropTypes.func.isRequired,
+ setIndexerSchemaSort: PropTypes.func.isRequired,
+ onModalClose: PropTypes.func.isRequired,
+ onSelectIndexer: PropTypes.func.isRequired
+};
+
+export default connect(createMapStateToProps, mapDispatchToProps)(AddIndexerModalContentConnector);
diff --git a/frontend/src/Indexer/Add/SelectIndexerRow.tsx b/frontend/src/Indexer/Add/SelectIndexerRow.tsx
index 157050e41..ab6850573 100644
--- a/frontend/src/Indexer/Add/SelectIndexerRow.tsx
+++ b/frontend/src/Indexer/Add/SelectIndexerRow.tsx
@@ -2,19 +2,18 @@ import React, { useCallback } from 'react';
import Icon from 'Components/Icon';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRowButton from 'Components/Table/TableRowButton';
-import DownloadProtocol from 'DownloadClient/DownloadProtocol';
import { icons } from 'Helpers/Props';
import CapabilitiesLabel from 'Indexer/Index/Table/CapabilitiesLabel';
-import PrivacyLabel from 'Indexer/Index/Table/PrivacyLabel';
import ProtocolLabel from 'Indexer/Index/Table/ProtocolLabel';
-import { IndexerCapabilities, IndexerPrivacy } from 'Indexer/Indexer';
+import { IndexerCapabilities } from 'Indexer/Indexer';
+import firstCharToUpper from 'Utilities/String/firstCharToUpper';
import translate from 'Utilities/String/translate';
import styles from './SelectIndexerRow.css';
interface SelectIndexerRowProps {
name: string;
- protocol: DownloadProtocol;
- privacy: IndexerPrivacy;
+ protocol: string;
+ privacy: string;
language: string;
description: string;
capabilities: IndexerCapabilities;
@@ -64,9 +63,7 @@ function SelectIndexerRow(props: SelectIndexerRowProps) {
{description}
-
-
-
+ {translate(firstCharToUpper(privacy))}
diff --git a/frontend/src/Indexer/Edit/EditIndexerModalContent.js b/frontend/src/Indexer/Edit/EditIndexerModalContent.js
index 7dabc50d9..b09b9e656 100644
--- a/frontend/src/Indexer/Edit/EditIndexerModalContent.js
+++ b/frontend/src/Indexer/Edit/EditIndexerModalContent.js
@@ -97,7 +97,7 @@ function EditIndexerModalContent(props) {
@@ -144,7 +144,6 @@ function EditIndexerModalContent(props) {
}) :
null
}
-
{
);
const [isSelectMode, setIsSelectMode] = useState(false);
- useEffect(() => {
- dispatch(fetchIndexers());
- dispatch(fetchIndexerStatus());
- }, [dispatch]);
-
const onAddIndexerPress = useCallback(() => {
setIsAddIndexerModalOpen(true);
}, [setIsAddIndexerModalOpen]);
diff --git a/frontend/src/Indexer/Index/Select/Edit/EditIndexerModalContent.tsx b/frontend/src/Indexer/Index/Select/Edit/EditIndexerModalContent.tsx
index 9d42aa389..b0bba5b94 100644
--- a/frontend/src/Indexer/Index/Select/Edit/EditIndexerModalContent.tsx
+++ b/frontend/src/Indexer/Index/Select/Edit/EditIndexerModalContent.tsx
@@ -19,7 +19,6 @@ interface SavePayload {
seedRatio?: number;
seedTime?: number;
packSeedTime?: number;
- preferMagnetUrl?: boolean;
}
interface EditIndexerModalContentProps {
@@ -36,7 +35,7 @@ const enableOptions = [
get value() {
return translate('NoChange');
},
- isDisabled: true,
+ disabled: true,
},
{
key: 'true',
@@ -66,9 +65,6 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
const [packSeedTime, setPackSeedTime] = useState(
null
);
- const [preferMagnetUrl, setPreferMagnetUrl] = useState<
- null | string | boolean
- >(null);
const save = useCallback(() => {
let hasChanges = false;
@@ -109,11 +105,6 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
payload.packSeedTime = packSeedTime as number;
}
- if (preferMagnetUrl !== null) {
- hasChanges = true;
- payload.preferMagnetUrl = preferMagnetUrl === 'true';
- }
-
if (hasChanges) {
onSavePress(payload);
}
@@ -127,7 +118,6 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
seedRatio,
seedTime,
packSeedTime,
- preferMagnetUrl,
onSavePress,
onModalClose,
]);
@@ -156,9 +146,6 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
case 'packSeedTime':
setPackSeedTime(value);
break;
- case 'preferMagnetUrl':
- setPreferMagnetUrl(value);
- break;
default:
console.warn(`EditIndexersModalContent Unknown Input: '${name}'`);
}
@@ -237,7 +224,6 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
name="seedRatio"
value={seedRatio}
helpText={translate('SeedRatioHelpText')}
- isFloat={true}
onChange={onInputChange}
/>
@@ -267,18 +253,6 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
onChange={onInputChange}
/>
-
-
- {translate('PreferMagnetUrl')}
-
-
-
diff --git a/frontend/src/Indexer/Index/Table/CapabilitiesLabel.tsx b/frontend/src/Indexer/Index/Table/CapabilitiesLabel.tsx
index 8e30532cc..c832806ed 100644
--- a/frontend/src/Indexer/Index/Table/CapabilitiesLabel.tsx
+++ b/frontend/src/Indexer/Index/Table/CapabilitiesLabel.tsx
@@ -2,7 +2,6 @@ import { uniqBy } from 'lodash';
import React from 'react';
import Label from 'Components/Label';
import { IndexerCapabilities } from 'Indexer/Indexer';
-import translate from 'Utilities/String/translate';
interface CapabilitiesLabelProps {
capabilities: IndexerCapabilities;
@@ -39,7 +38,7 @@ function CapabilitiesLabel(props: CapabilitiesLabelProps) {
);
})}
- {filteredList.length === 0 ? : null}
+ {filteredList.length === 0 ? : null}
);
}
diff --git a/frontend/src/Indexer/Index/Table/IndexerIndexRow.css b/frontend/src/Indexer/Index/Table/IndexerIndexRow.css
index a20efded3..a09d0218e 100644
--- a/frontend/src/Indexer/Index/Table/IndexerIndexRow.css
+++ b/frontend/src/Indexer/Index/Table/IndexerIndexRow.css
@@ -29,8 +29,7 @@
.minimumSeeders,
.seedRatio,
.seedTime,
-.packSeedTime,
-.preferMagnetUrl {
+.packSeedTime {
composes: cell;
flex: 0 0 90px;
diff --git a/frontend/src/Indexer/Index/Table/IndexerIndexRow.css.d.ts b/frontend/src/Indexer/Index/Table/IndexerIndexRow.css.d.ts
index 42821bd74..5feb0c35d 100644
--- a/frontend/src/Indexer/Index/Table/IndexerIndexRow.css.d.ts
+++ b/frontend/src/Indexer/Index/Table/IndexerIndexRow.css.d.ts
@@ -11,7 +11,6 @@ interface CssExports {
'id': string;
'minimumSeeders': string;
'packSeedTime': string;
- 'preferMagnetUrl': string;
'priority': string;
'privacy': string;
'protocol': string;
diff --git a/frontend/src/Indexer/Index/Table/IndexerIndexRow.tsx b/frontend/src/Indexer/Index/Table/IndexerIndexRow.tsx
index e4c3cd32e..9e83e9b8d 100644
--- a/frontend/src/Indexer/Index/Table/IndexerIndexRow.tsx
+++ b/frontend/src/Indexer/Index/Table/IndexerIndexRow.tsx
@@ -1,7 +1,7 @@
import React, { useCallback, useState } from 'react';
import { useSelector } from 'react-redux';
import { useSelect } from 'App/SelectContext';
-import CheckInput from 'Components/Form/CheckInput';
+import Label from 'Components/Label';
import IconButton from 'Components/Link/IconButton';
import RelativeDateCell from 'Components/Table/Cells/RelativeDateCell';
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
@@ -15,10 +15,10 @@ import createIndexerIndexItemSelector from 'Indexer/Index/createIndexerIndexItem
import Indexer from 'Indexer/Indexer';
import IndexerTitleLink from 'Indexer/IndexerTitleLink';
import { SelectStateInputProps } from 'typings/props';
+import firstCharToUpper from 'Utilities/String/firstCharToUpper';
import translate from 'Utilities/String/translate';
import CapabilitiesLabel from './CapabilitiesLabel';
import IndexerStatusCell from './IndexerStatusCell';
-import PrivacyLabel from './PrivacyLabel';
import ProtocolLabel from './ProtocolLabel';
import styles from './IndexerIndexRow.css';
@@ -75,10 +75,6 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
fields.find((field) => field.name === 'torrentBaseSettings.packSeedTime')
?.value ?? undefined;
- const preferMagnetUrl =
- fields.find((field) => field.name === 'torrentBaseSettings.preferMagnetUrl')
- ?.value ?? undefined;
-
const rssUrl = `${window.location.origin}${
window.Prowlarr.urlBase
}/${id}/api?apikey=${encodeURIComponent(
@@ -107,10 +103,6 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
setIsDeleteIndexerModalOpen(false);
}, [setIsDeleteIndexerModalOpen]);
- const checkInputCallback = useCallback(() => {
- // Mock handler to satisfy `onChange` being required for `CheckInput`.
- }, []);
-
const onSelectedChange = useCallback(
({ id, value, shiftKey }: SelectStateInputProps) => {
selectDispatch({
@@ -183,7 +175,7 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
if (name === 'privacy') {
return (
-
+
);
}
@@ -286,21 +278,6 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
);
}
- if (name === 'preferMagnetUrl') {
- return (
-
- {preferMagnetUrl === undefined ? null : (
-
- )}
-
- );
- }
-
if (name === 'actions') {
return (
columns
);
-function Row({ index, style, data }: ListChildComponentProps) {
+const Row: React.FC> = ({
+ index,
+ style,
+ data,
+}) => {
const { items, sortKey, columns, isSelectMode, onCloneIndexerPress } = data;
if (index >= items.length) {
@@ -73,7 +77,7 @@ function Row({ index, style, data }: ListChildComponentProps) {
/>
);
-}
+};
function getWindowScrollTopPosition() {
return document.documentElement.scrollTop || document.body.scrollTop || 0;
diff --git a/frontend/src/Indexer/Index/Table/IndexerIndexTableHeader.css b/frontend/src/Indexer/Index/Table/IndexerIndexTableHeader.css
index 839cd49ff..185ba0ef7 100644
--- a/frontend/src/Indexer/Index/Table/IndexerIndexTableHeader.css
+++ b/frontend/src/Indexer/Index/Table/IndexerIndexTableHeader.css
@@ -22,8 +22,7 @@
.minimumSeeders,
.seedRatio,
.seedTime,
-.packSeedTime,
-.preferMagnetUrl {
+.packSeedTime {
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
flex: 0 0 90px;
diff --git a/frontend/src/Indexer/Index/Table/IndexerIndexTableHeader.css.d.ts b/frontend/src/Indexer/Index/Table/IndexerIndexTableHeader.css.d.ts
index 020d61f27..f6a54fa25 100644
--- a/frontend/src/Indexer/Index/Table/IndexerIndexTableHeader.css.d.ts
+++ b/frontend/src/Indexer/Index/Table/IndexerIndexTableHeader.css.d.ts
@@ -8,7 +8,6 @@ interface CssExports {
'id': string;
'minimumSeeders': string;
'packSeedTime': string;
- 'preferMagnetUrl': string;
'priority': string;
'privacy': string;
'protocol': string;
diff --git a/frontend/src/Indexer/Index/Table/IndexerIndexTableOptions.tsx b/frontend/src/Indexer/Index/Table/IndexerIndexTableOptions.tsx
index 3aa087790..6155929df 100644
--- a/frontend/src/Indexer/Index/Table/IndexerIndexTableOptions.tsx
+++ b/frontend/src/Indexer/Index/Table/IndexerIndexTableOptions.tsx
@@ -1,4 +1,4 @@
-import React, { useCallback } from 'react';
+import React, { Fragment, useCallback } from 'react';
import { useSelector } from 'react-redux';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -32,17 +32,19 @@ function IndexerIndexTableOptions(props: IndexerIndexTableOptionsProps) {
);
return (
-