-
-
-
- {translate('ActiveIndexers')}
-
-
{indexerCount}
-
-
-
-
-
- {translate('TotalQueries')}
-
-
- {abbreviateNumber(queryCount)}
-
-
-
-
-
-
- {translate('TotalGrabs')}
-
-
{abbreviateNumber(grabCount)}
-
-
-
-
-
- {translate('ActiveApps')}
-
-
{userAgentCount}
-
+
+
-
)}
diff --git a/frontend/src/Indexer/Stats/IndexerStatsFilterMenu.tsx b/frontend/src/Indexer/Stats/IndexerStatsFilterMenu.tsx
new file mode 100644
index 000000000..7b30be4c3
--- /dev/null
+++ b/frontend/src/Indexer/Stats/IndexerStatsFilterMenu.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import FilterMenu from 'Components/Menu/FilterMenu';
+import { align } from 'Helpers/Props';
+
+interface IndexerStatsFilterMenuProps {
+ selectedFilterKey: string | number;
+ filters: object[];
+ isDisabled: boolean;
+ onFilterSelect(filterName: string): unknown;
+}
+
+function IndexerStatsFilterMenu(props: IndexerStatsFilterMenuProps) {
+ const { selectedFilterKey, filters, isDisabled, onFilterSelect } = props;
+
+ return (
+
+ );
+}
+
+export default IndexerStatsFilterMenu;
diff --git a/frontend/src/Indexer/Stats/IndexerStatsFilterModal.tsx b/frontend/src/Indexer/Stats/IndexerStatsFilterModal.tsx
deleted file mode 100644
index 6e3a49dfb..000000000
--- a/frontend/src/Indexer/Stats/IndexerStatsFilterModal.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import React, { useCallback } from 'react';
-import { useDispatch, useSelector } from 'react-redux';
-import { createSelector } from 'reselect';
-import AppState from 'App/State/AppState';
-import FilterModal from 'Components/Filter/FilterModal';
-import { setIndexerStatsFilter } from 'Store/Actions/indexerStatsActions';
-
-function createIndexerStatsSelector() {
- return createSelector(
- (state: AppState) => state.indexerStats.item,
- (indexerStats) => {
- return indexerStats;
- }
- );
-}
-
-function createFilterBuilderPropsSelector() {
- return createSelector(
- (state: AppState) => state.indexerStats.filterBuilderProps,
- (filterBuilderProps) => {
- return filterBuilderProps;
- }
- );
-}
-
-interface IndexerStatsFilterModalProps {
- isOpen: boolean;
-}
-
-export default function IndexerStatsFilterModal(
- props: IndexerStatsFilterModalProps
-) {
- const sectionItems = [useSelector(createIndexerStatsSelector())];
- const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
- const customFilterType = 'indexerStats';
-
- const dispatch = useDispatch();
-
- const dispatchSetFilter = useCallback(
- (payload: unknown) => {
- dispatch(setIndexerStatsFilter(payload));
- },
- [dispatch]
- );
-
- return (
-
- );
-}
diff --git a/frontend/src/Indexer/useIndexer.ts b/frontend/src/Indexer/useIndexer.ts
deleted file mode 100644
index a1b2ffa9d..000000000
--- a/frontend/src/Indexer/useIndexer.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { useSelector } from 'react-redux';
-import { createSelector } from 'reselect';
-import AppState from 'App/State/AppState';
-
-export function createIndexerSelector(indexerId?: number) {
- return createSelector(
- (state: AppState) => state.indexers.itemMap,
- (state: AppState) => state.indexers.items,
- (itemMap, allIndexers) => {
- return indexerId ? allIndexers[itemMap[indexerId]] : undefined;
- }
- );
-}
-
-function useIndexer(indexerId?: number) {
- return useSelector(createIndexerSelector(indexerId));
-}
-
-export default useIndexer;
diff --git a/frontend/src/Search/Mobile/SearchIndexOverview.css b/frontend/src/Search/Mobile/SearchIndexOverview.css
index e29ff1ef9..4e184bd0a 100644
--- a/frontend/src/Search/Mobile/SearchIndexOverview.css
+++ b/frontend/src/Search/Mobile/SearchIndexOverview.css
@@ -47,42 +47,3 @@ $hoverScale: 1.05;
right: 0;
white-space: nowrap;
}
-
-.downloadLink {
- composes: link from '~Components/Link/Link.css';
-
- margin: 0 2px;
- width: 22px;
- color: var(--textColor);
- text-align: center;
-}
-
-.manualDownloadContent {
- position: relative;
- display: inline-block;
- margin: 0 2px;
- width: 22px;
- height: 20.39px;
- vertical-align: middle;
- line-height: 20.39px;
-
- &:hover {
- color: var(--iconButtonHoverColor);
- }
-}
-
-.interactiveIcon {
- position: absolute;
- top: 4px;
- left: 0;
- /* width: 100%; */
- text-align: center;
-}
-
-.downloadIcon {
- position: absolute;
- top: 7px;
- left: 8px;
- /* width: 100%; */
- text-align: center;
-}
diff --git a/frontend/src/Search/Mobile/SearchIndexOverview.css.d.ts b/frontend/src/Search/Mobile/SearchIndexOverview.css.d.ts
index 68256eb25..266cf7fca 100644
--- a/frontend/src/Search/Mobile/SearchIndexOverview.css.d.ts
+++ b/frontend/src/Search/Mobile/SearchIndexOverview.css.d.ts
@@ -4,13 +4,9 @@ interface CssExports {
'actions': string;
'container': string;
'content': string;
- 'downloadIcon': string;
- 'downloadLink': string;
'indexerRow': string;
'info': string;
'infoRow': string;
- 'interactiveIcon': string;
- 'manualDownloadContent': string;
'title': string;
'titleRow': string;
}
diff --git a/frontend/src/Search/Mobile/SearchIndexOverview.js b/frontend/src/Search/Mobile/SearchIndexOverview.js
new file mode 100644
index 000000000..1a14ae66c
--- /dev/null
+++ b/frontend/src/Search/Mobile/SearchIndexOverview.js
@@ -0,0 +1,234 @@
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import TextTruncate from 'react-text-truncate';
+import Label from 'Components/Label';
+import IconButton from 'Components/Link/IconButton';
+import Link from 'Components/Link/Link';
+import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
+import { icons, kinds } from 'Helpers/Props';
+import ProtocolLabel from 'Indexer/Index/Table/ProtocolLabel';
+import CategoryLabel from 'Search/Table/CategoryLabel';
+import Peers from 'Search/Table/Peers';
+import dimensions from 'Styles/Variables/dimensions';
+import formatAge from 'Utilities/Number/formatAge';
+import formatBytes from 'Utilities/Number/formatBytes';
+import titleCase from 'Utilities/String/titleCase';
+import translate from 'Utilities/String/translate';
+import styles from './SearchIndexOverview.css';
+
+const columnPadding = parseInt(dimensions.movieIndexColumnPadding);
+const columnPaddingSmallScreen = parseInt(dimensions.movieIndexColumnPaddingSmallScreen);
+
+function getContentHeight(rowHeight, isSmallScreen) {
+ const padding = isSmallScreen ? columnPaddingSmallScreen : columnPadding;
+
+ return rowHeight - padding;
+}
+
+function getDownloadIcon(isGrabbing, isGrabbed, grabError) {
+ if (isGrabbing) {
+ return icons.SPINNER;
+ } else if (isGrabbed) {
+ return icons.DOWNLOADING;
+ } else if (grabError) {
+ return icons.DOWNLOADING;
+ }
+
+ return icons.DOWNLOAD;
+}
+
+function getDownloadKind(isGrabbed, grabError) {
+ if (isGrabbed) {
+ return kinds.SUCCESS;
+ }
+
+ if (grabError) {
+ return kinds.DANGER;
+ }
+
+ return kinds.DEFAULT;
+}
+
+function getDownloadTooltip(isGrabbing, isGrabbed, grabError) {
+ if (isGrabbing) {
+ return '';
+ } else if (isGrabbed) {
+ return translate('AddedToDownloadClient');
+ } else if (grabError) {
+ return grabError;
+ }
+
+ return translate('AddToDownloadClient');
+}
+
+class SearchIndexOverview extends Component {
+
+ //
+ // Listeners
+
+ onGrabPress = () => {
+ const {
+ guid,
+ indexerId,
+ onGrabPress
+ } = this.props;
+
+ onGrabPress({
+ guid,
+ indexerId
+ });
+ };
+
+ //
+ // Render
+
+ render() {
+ const {
+ title,
+ infoUrl,
+ protocol,
+ downloadUrl,
+ magnetUrl,
+ categories,
+ seeders,
+ leechers,
+ indexerFlags,
+ size,
+ age,
+ ageHours,
+ ageMinutes,
+ indexer,
+ rowHeight,
+ isSmallScreen,
+ isGrabbed,
+ isGrabbing,
+ grabError
+ } = this.props;
+
+ const contentHeight = getContentHeight(rowHeight, isSmallScreen);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ downloadUrl || magnetUrl ?
+ :
+ null
+ }
+
+
+
+ {indexer}
+
+
+
+
+ {
+ protocol === 'torrent' &&
+
+ }
+
+
+
+
+
+
+
+ {
+ indexerFlags.length ?
+ indexerFlags
+ .sort((a, b) => a.localeCompare(b))
+ .map((flag, index) => {
+ return (
+
+ );
+ }) :
+ null
+ }
+
+
+
+
+ );
+ }
+}
+
+SearchIndexOverview.propTypes = {
+ guid: PropTypes.string.isRequired,
+ categories: PropTypes.arrayOf(PropTypes.object).isRequired,
+ protocol: PropTypes.string.isRequired,
+ age: PropTypes.number.isRequired,
+ ageHours: PropTypes.number.isRequired,
+ ageMinutes: PropTypes.number.isRequired,
+ publishDate: PropTypes.string.isRequired,
+ title: PropTypes.string.isRequired,
+ infoUrl: PropTypes.string.isRequired,
+ downloadUrl: PropTypes.string,
+ magnetUrl: PropTypes.string,
+ indexerId: PropTypes.number.isRequired,
+ indexer: PropTypes.string.isRequired,
+ size: PropTypes.number.isRequired,
+ files: PropTypes.number,
+ grabs: PropTypes.number,
+ seeders: PropTypes.number,
+ leechers: PropTypes.number,
+ indexerFlags: PropTypes.arrayOf(PropTypes.string).isRequired,
+ rowHeight: PropTypes.number.isRequired,
+ showRelativeDates: PropTypes.bool.isRequired,
+ shortDateFormat: PropTypes.string.isRequired,
+ longDateFormat: PropTypes.string.isRequired,
+ timeFormat: PropTypes.string.isRequired,
+ isSmallScreen: PropTypes.bool.isRequired,
+ onGrabPress: PropTypes.func.isRequired,
+ isGrabbing: PropTypes.bool.isRequired,
+ isGrabbed: PropTypes.bool.isRequired,
+ grabError: PropTypes.string
+};
+
+SearchIndexOverview.defaultProps = {
+ isGrabbing: false,
+ isGrabbed: false
+};
+
+export default SearchIndexOverview;
diff --git a/frontend/src/Search/Mobile/SearchIndexOverview.tsx b/frontend/src/Search/Mobile/SearchIndexOverview.tsx
deleted file mode 100644
index 21a42d70c..000000000
--- a/frontend/src/Search/Mobile/SearchIndexOverview.tsx
+++ /dev/null
@@ -1,264 +0,0 @@
-import React, { useCallback, useMemo, useState } from 'react';
-import { useSelector } from 'react-redux';
-import TextTruncate from 'react-text-truncate';
-import Icon from 'Components/Icon';
-import Label from 'Components/Label';
-import IconButton from 'Components/Link/IconButton';
-import Link from 'Components/Link/Link';
-import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
-import DownloadProtocol from 'DownloadClient/DownloadProtocol';
-import { icons, kinds } from 'Helpers/Props';
-import ProtocolLabel from 'Indexer/Index/Table/ProtocolLabel';
-import { IndexerCategory } from 'Indexer/Indexer';
-import OverrideMatchModal from 'Search/OverrideMatch/OverrideMatchModal';
-import CategoryLabel from 'Search/Table/CategoryLabel';
-import Peers from 'Search/Table/Peers';
-import createEnabledDownloadClientsSelector from 'Store/Selectors/createEnabledDownloadClientsSelector';
-import dimensions from 'Styles/Variables/dimensions';
-import formatDateTime from 'Utilities/Date/formatDateTime';
-import formatAge from 'Utilities/Number/formatAge';
-import formatBytes from 'Utilities/Number/formatBytes';
-import titleCase from 'Utilities/String/titleCase';
-import translate from 'Utilities/String/translate';
-import styles from './SearchIndexOverview.css';
-
-const columnPadding = parseInt(dimensions.movieIndexColumnPadding);
-const columnPaddingSmallScreen = parseInt(
- dimensions.movieIndexColumnPaddingSmallScreen
-);
-
-function getDownloadIcon(
- isGrabbing: boolean,
- isGrabbed: boolean,
- grabError?: string
-) {
- if (isGrabbing) {
- return icons.SPINNER;
- } else if (isGrabbed) {
- return icons.DOWNLOADING;
- } else if (grabError) {
- return icons.DOWNLOADING;
- }
-
- return icons.DOWNLOAD;
-}
-
-function getDownloadKind(isGrabbed: boolean, grabError?: string) {
- if (isGrabbed) {
- return kinds.SUCCESS;
- }
-
- if (grabError) {
- return kinds.DANGER;
- }
-
- return kinds.DEFAULT;
-}
-
-function getDownloadTooltip(
- isGrabbing: boolean,
- isGrabbed: boolean,
- grabError?: string
-) {
- if (isGrabbing) {
- return '';
- } else if (isGrabbed) {
- return translate('AddedToDownloadClient');
- } else if (grabError) {
- return grabError;
- }
-
- return translate('AddToDownloadClient');
-}
-
-interface SearchIndexOverviewProps {
- guid: string;
- protocol: DownloadProtocol;
- age: number;
- ageHours: number;
- ageMinutes: number;
- publishDate: string;
- title: string;
- infoUrl: string;
- downloadUrl?: string;
- magnetUrl?: string;
- indexerId: number;
- indexer: string;
- categories: IndexerCategory[];
- size: number;
- seeders?: number;
- leechers?: number;
- indexerFlags: string[];
- isGrabbing: boolean;
- isGrabbed: boolean;
- grabError?: string;
- longDateFormat: string;
- timeFormat: string;
- rowHeight: number;
- isSmallScreen: boolean;
- onGrabPress(...args: unknown[]): void;
-}
-
-function SearchIndexOverview(props: SearchIndexOverviewProps) {
- const {
- guid,
- indexerId,
- protocol,
- categories,
- age,
- ageHours,
- ageMinutes,
- publishDate,
- title,
- infoUrl,
- downloadUrl,
- magnetUrl,
- indexer,
- size,
- seeders,
- leechers,
- indexerFlags = [],
- isGrabbing = false,
- isGrabbed = false,
- grabError,
- longDateFormat,
- timeFormat,
- rowHeight,
- isSmallScreen,
- onGrabPress,
- } = props;
-
- const [isOverrideModalOpen, setIsOverrideModalOpen] = useState(false);
-
- const { items: downloadClients } = useSelector(
- createEnabledDownloadClientsSelector(protocol)
- );
-
- const onGrabPressWrapper = useCallback(() => {
- onGrabPress({
- guid,
- indexerId,
- });
- }, [guid, indexerId, onGrabPress]);
-
- const onOverridePress = useCallback(() => {
- setIsOverrideModalOpen(true);
- }, [setIsOverrideModalOpen]);
-
- const onOverrideModalClose = useCallback(() => {
- setIsOverrideModalOpen(false);
- }, [setIsOverrideModalOpen]);
-
- const contentHeight = useMemo(() => {
- const padding = isSmallScreen ? columnPaddingSmallScreen : columnPadding;
-
- return rowHeight - padding;
- }, [rowHeight, isSmallScreen]);
-
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
- {downloadClients.length > 1 ? (
-
-
-
-
-
-
-
- ) : null}
-
- {downloadUrl || magnetUrl ? (
-
- ) : null}
-
-
-
{indexer}
-
-
-
- {protocol === 'torrent' && (
-
- )}
-
-
-
-
-
-
-
- {indexerFlags.length
- ? indexerFlags
- .sort((a, b) =>
- a.localeCompare(b, undefined, { numeric: true })
- )
- .map((flag, index) => {
- return (
-
- );
- })
- : null}
-
-
-
-
-
-
- >
- );
-}
-
-export default SearchIndexOverview;
diff --git a/frontend/src/Search/NoSearchResults.css b/frontend/src/Search/NoSearchResults.css
index f17dd633e..eff6272f7 100644
--- a/frontend/src/Search/NoSearchResults.css
+++ b/frontend/src/Search/NoSearchResults.css
@@ -1,6 +1,4 @@
.message {
- composes: alert from '~Components/Alert.css';
-
margin-top: 10px;
margin-bottom: 30px;
text-align: center;
diff --git a/frontend/src/Search/NoSearchResults.tsx b/frontend/src/Search/NoSearchResults.tsx
index 46fbc85e0..4ffd1d7fd 100644
--- a/frontend/src/Search/NoSearchResults.tsx
+++ b/frontend/src/Search/NoSearchResults.tsx
@@ -1,6 +1,4 @@
import React from 'react';
-import Alert from 'Components/Alert';
-import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './NoSearchResults.css';
@@ -13,16 +11,18 @@ function NoSearchResults(props: NoSearchResultsProps) {
if (totalItems > 0) {
return (
-
- {translate('AllSearchResultsHiddenByFilter')}
-
+
+
+ {translate('AllIndexersHiddenDueToFilter')}
+
+
);
}
return (
-
- {translate('NoSearchResultsFound')}
-
+
+
{translate('NoSearchResultsFound')}
+
);
}
diff --git a/frontend/src/Search/OverrideMatch/DownloadClient/SelectDownloadClientModal.tsx b/frontend/src/Search/OverrideMatch/DownloadClient/SelectDownloadClientModal.tsx
deleted file mode 100644
index 7d623decd..000000000
--- a/frontend/src/Search/OverrideMatch/DownloadClient/SelectDownloadClientModal.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import React from 'react';
-import Modal from 'Components/Modal/Modal';
-import DownloadProtocol from 'DownloadClient/DownloadProtocol';
-import { sizes } from 'Helpers/Props';
-import SelectDownloadClientModalContent from './SelectDownloadClientModalContent';
-
-interface SelectDownloadClientModalProps {
- isOpen: boolean;
- protocol: DownloadProtocol;
- modalTitle: string;
- onDownloadClientSelect(downloadClientId: number): void;
- onModalClose(): void;
-}
-
-function SelectDownloadClientModal(props: SelectDownloadClientModalProps) {
- const { isOpen, protocol, modalTitle, onDownloadClientSelect, onModalClose } =
- props;
-
- return (
-
-
-
- );
-}
-
-export default SelectDownloadClientModal;
diff --git a/frontend/src/Search/OverrideMatch/DownloadClient/SelectDownloadClientModalContent.tsx b/frontend/src/Search/OverrideMatch/DownloadClient/SelectDownloadClientModalContent.tsx
deleted file mode 100644
index 63e15808f..000000000
--- a/frontend/src/Search/OverrideMatch/DownloadClient/SelectDownloadClientModalContent.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import React from 'react';
-import { useSelector } from 'react-redux';
-import Alert from 'Components/Alert';
-import Form from 'Components/Form/Form';
-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 DownloadProtocol from 'DownloadClient/DownloadProtocol';
-import { kinds } from 'Helpers/Props';
-import createEnabledDownloadClientsSelector from 'Store/Selectors/createEnabledDownloadClientsSelector';
-import translate from 'Utilities/String/translate';
-import SelectDownloadClientRow from './SelectDownloadClientRow';
-
-interface SelectDownloadClientModalContentProps {
- protocol: DownloadProtocol;
- modalTitle: string;
- onDownloadClientSelect(downloadClientId: number): void;
- onModalClose(): void;
-}
-
-function SelectDownloadClientModalContent(
- props: SelectDownloadClientModalContentProps
-) {
- const { modalTitle, protocol, onDownloadClientSelect, onModalClose } = props;
-
- const { isFetching, isPopulated, error, items } = useSelector(
- createEnabledDownloadClientsSelector(protocol)
- );
-
- return (
-
-
- {translate('SelectDownloadClientModalTitle', { modalTitle })}
-
-
-
- {isFetching ? : null}
-
- {!isFetching && error ? (
-
- {translate('DownloadClientsLoadError')}
-
- ) : null}
-
- {isPopulated && !error ? (
-
- ) : null}
-
-
-
-
-
-
- );
-}
-
-export default SelectDownloadClientModalContent;
diff --git a/frontend/src/Search/OverrideMatch/DownloadClient/SelectDownloadClientRow.css b/frontend/src/Search/OverrideMatch/DownloadClient/SelectDownloadClientRow.css
deleted file mode 100644
index 6525db977..000000000
--- a/frontend/src/Search/OverrideMatch/DownloadClient/SelectDownloadClientRow.css
+++ /dev/null
@@ -1,6 +0,0 @@
-.downloadClient {
- display: flex;
- justify-content: space-between;
- padding: 8px;
- border-bottom: 1px solid var(--borderColor);
-}
diff --git a/frontend/src/Search/OverrideMatch/DownloadClient/SelectDownloadClientRow.tsx b/frontend/src/Search/OverrideMatch/DownloadClient/SelectDownloadClientRow.tsx
deleted file mode 100644
index 6f98d60b4..000000000
--- a/frontend/src/Search/OverrideMatch/DownloadClient/SelectDownloadClientRow.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import React, { useCallback } from 'react';
-import Link from 'Components/Link/Link';
-import translate from 'Utilities/String/translate';
-import styles from './SelectDownloadClientRow.css';
-
-interface SelectSeasonRowProps {
- id: number;
- name: string;
- priority: number;
- onDownloadClientSelect(downloadClientId: number): unknown;
-}
-
-function SelectDownloadClientRow(props: SelectSeasonRowProps) {
- const { id, name, priority, onDownloadClientSelect } = props;
-
- const onSeasonSelectWrapper = useCallback(() => {
- onDownloadClientSelect(id);
- }, [id, onDownloadClientSelect]);
-
- return (
-
-
{name}
-
{translate('PrioritySettings', { priority })}
-
- );
-}
-
-export default SelectDownloadClientRow;
diff --git a/frontend/src/Search/OverrideMatch/OverrideMatchData.css b/frontend/src/Search/OverrideMatch/OverrideMatchData.css
deleted file mode 100644
index bd4d2f788..000000000
--- a/frontend/src/Search/OverrideMatch/OverrideMatchData.css
+++ /dev/null
@@ -1,17 +0,0 @@
-.link {
- composes: link from '~Components/Link/Link.css';
-
- width: 100%;
-}
-
-.placeholder {
- display: inline-block;
- margin: -2px 0;
- width: 100%;
- outline: 2px dashed var(--dangerColor);
- outline-offset: -2px;
-}
-
-.optional {
- outline: 2px dashed var(--gray);
-}
diff --git a/frontend/src/Search/OverrideMatch/OverrideMatchData.css.d.ts b/frontend/src/Search/OverrideMatch/OverrideMatchData.css.d.ts
deleted file mode 100644
index dd3ac4575..000000000
--- a/frontend/src/Search/OverrideMatch/OverrideMatchData.css.d.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-// This file is automatically generated.
-// Please do not change this file!
-interface CssExports {
- 'link': string;
- 'optional': string;
- 'placeholder': string;
-}
-export const cssExports: CssExports;
-export default cssExports;
diff --git a/frontend/src/Search/OverrideMatch/OverrideMatchData.tsx b/frontend/src/Search/OverrideMatch/OverrideMatchData.tsx
deleted file mode 100644
index 82d6bd812..000000000
--- a/frontend/src/Search/OverrideMatch/OverrideMatchData.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import classNames from 'classnames';
-import React from 'react';
-import Link from 'Components/Link/Link';
-import styles from './OverrideMatchData.css';
-
-interface OverrideMatchDataProps {
- value?: string | number | JSX.Element | JSX.Element[];
- isDisabled?: boolean;
- isOptional?: boolean;
- onPress: () => void;
-}
-
-function OverrideMatchData(props: OverrideMatchDataProps) {
- const { value, isDisabled = false, isOptional, onPress } = props;
-
- return (
-
- {(value == null || (Array.isArray(value) && value.length === 0)) &&
- !isDisabled ? (
-
-
-
- ) : (
- value
- )}
-
- );
-}
-
-export default OverrideMatchData;
diff --git a/frontend/src/Search/OverrideMatch/OverrideMatchModal.tsx b/frontend/src/Search/OverrideMatch/OverrideMatchModal.tsx
deleted file mode 100644
index 16d62ea7c..000000000
--- a/frontend/src/Search/OverrideMatch/OverrideMatchModal.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import React from 'react';
-import Modal from 'Components/Modal/Modal';
-import DownloadProtocol from 'DownloadClient/DownloadProtocol';
-import { sizes } from 'Helpers/Props';
-import OverrideMatchModalContent from './OverrideMatchModalContent';
-
-interface OverrideMatchModalProps {
- isOpen: boolean;
- title: string;
- indexerId: number;
- guid: string;
- protocol: DownloadProtocol;
- isGrabbing: boolean;
- grabError?: string;
- onModalClose(): void;
-}
-
-function OverrideMatchModal(props: OverrideMatchModalProps) {
- const {
- isOpen,
- title,
- indexerId,
- guid,
- protocol,
- isGrabbing,
- grabError,
- onModalClose,
- } = props;
-
- return (
-
-
-
- );
-}
-
-export default OverrideMatchModal;
diff --git a/frontend/src/Search/OverrideMatch/OverrideMatchModalContent.css b/frontend/src/Search/OverrideMatch/OverrideMatchModalContent.css
deleted file mode 100644
index a5b4b8d52..000000000
--- a/frontend/src/Search/OverrideMatch/OverrideMatchModalContent.css
+++ /dev/null
@@ -1,49 +0,0 @@
-.label {
- composes: label from '~Components/Label.css';
-
- cursor: pointer;
-}
-
-.item {
- display: block;
- margin-bottom: 5px;
- margin-left: 50px;
-}
-
-.footer {
- composes: modalFooter from '~Components/Modal/ModalFooter.css';
-
- display: flex;
- justify-content: space-between;
- overflow: hidden;
-}
-
-.error {
- margin-right: 20px;
- color: var(--dangerColor);
- word-break: break-word;
-}
-
-.buttons {
- display: flex;
-}
-
-@media only screen and (max-width: $breakpointSmall) {
- .item {
- margin-left: 0;
- }
-
- .footer {
- display: block;
- }
-
- .error {
- margin-right: 0;
- margin-bottom: 10px;
- }
-
- .buttons {
- justify-content: space-between;
- flex-grow: 1;
- }
-}
diff --git a/frontend/src/Search/OverrideMatch/OverrideMatchModalContent.css.d.ts b/frontend/src/Search/OverrideMatch/OverrideMatchModalContent.css.d.ts
deleted file mode 100644
index 79c77d6b5..000000000
--- a/frontend/src/Search/OverrideMatch/OverrideMatchModalContent.css.d.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-// This file is automatically generated.
-// Please do not change this file!
-interface CssExports {
- 'buttons': string;
- 'error': string;
- 'footer': string;
- 'item': string;
- 'label': string;
-}
-export const cssExports: CssExports;
-export default cssExports;
diff --git a/frontend/src/Search/OverrideMatch/OverrideMatchModalContent.tsx b/frontend/src/Search/OverrideMatch/OverrideMatchModalContent.tsx
deleted file mode 100644
index fbe0ec450..000000000
--- a/frontend/src/Search/OverrideMatch/OverrideMatchModalContent.tsx
+++ /dev/null
@@ -1,150 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import { useDispatch, useSelector } from 'react-redux';
-import DescriptionList from 'Components/DescriptionList/DescriptionList';
-import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
-import Button from 'Components/Link/Button';
-import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
-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 DownloadProtocol from 'DownloadClient/DownloadProtocol';
-import usePrevious from 'Helpers/Hooks/usePrevious';
-import { grabRelease } from 'Store/Actions/releaseActions';
-import { fetchDownloadClients } from 'Store/Actions/settingsActions';
-import createEnabledDownloadClientsSelector from 'Store/Selectors/createEnabledDownloadClientsSelector';
-import translate from 'Utilities/String/translate';
-import SelectDownloadClientModal from './DownloadClient/SelectDownloadClientModal';
-import OverrideMatchData from './OverrideMatchData';
-import styles from './OverrideMatchModalContent.css';
-
-type SelectType = 'select' | 'downloadClient';
-
-interface OverrideMatchModalContentProps {
- indexerId: number;
- title: string;
- guid: string;
- protocol: DownloadProtocol;
- isGrabbing: boolean;
- grabError?: string;
- onModalClose(): void;
-}
-
-function OverrideMatchModalContent(props: OverrideMatchModalContentProps) {
- const modalTitle = translate('ManualGrab');
- const {
- indexerId,
- title,
- guid,
- protocol,
- isGrabbing,
- grabError,
- onModalClose,
- } = props;
-
- const [downloadClientId, setDownloadClientId] = useState
(null);
- const [selectModalOpen, setSelectModalOpen] = useState(
- null
- );
- const previousIsGrabbing = usePrevious(isGrabbing);
-
- const dispatch = useDispatch();
- const { items: downloadClients } = useSelector(
- createEnabledDownloadClientsSelector(protocol)
- );
-
- const onSelectModalClose = useCallback(() => {
- setSelectModalOpen(null);
- }, [setSelectModalOpen]);
-
- const onSelectDownloadClientPress = useCallback(() => {
- setSelectModalOpen('downloadClient');
- }, [setSelectModalOpen]);
-
- const onDownloadClientSelect = useCallback(
- (downloadClientId: number) => {
- setDownloadClientId(downloadClientId);
- setSelectModalOpen(null);
- },
- [setDownloadClientId, setSelectModalOpen]
- );
-
- const onGrabPress = useCallback(() => {
- dispatch(
- grabRelease({
- indexerId,
- guid,
- downloadClientId,
- })
- );
- }, [indexerId, guid, downloadClientId, dispatch]);
-
- useEffect(() => {
- if (!isGrabbing && previousIsGrabbing) {
- onModalClose();
- }
- }, [isGrabbing, previousIsGrabbing, onModalClose]);
-
- useEffect(
- () => {
- dispatch(fetchDownloadClients());
- },
- // eslint-disable-next-line react-hooks/exhaustive-deps
- []
- );
-
- return (
-
-
- {translate('OverrideGrabModalTitle', { title })}
-
-
-
-
- {downloadClients.length > 1 ? (
- downloadClient.id === downloadClientId
- )?.name ?? translate('Default')
- }
- onPress={onSelectDownloadClientPress}
- />
- }
- />
- ) : null}
-
-
-
-
- {grabError}
-
-
-
-
-
- {translate('GrabRelease')}
-
-
-
-
-
-
- );
-}
-
-export default OverrideMatchModalContent;
diff --git a/frontend/src/Search/SearchFooter.js b/frontend/src/Search/SearchFooter.js
index 872328446..5e949fc6e 100644
--- a/frontend/src/Search/SearchFooter.js
+++ b/frontend/src/Search/SearchFooter.js
@@ -212,11 +212,7 @@ class SearchFooter extends Component {
name="searchQuery"
value={searchQuery}
buttons={
-
+
@@ -279,7 +275,6 @@ class SearchFooter extends Component {
}
+
@@ -314,7 +314,7 @@ class SearchIndex extends Component {
selectedFilterKey={selectedFilterKey}
filters={filters}
customFilters={customFilters}
- isDisabled={hasNoSearchResults}
+ isDisabled={hasNoIndexer}
onFilterSelect={onFilterSelect}
/>
diff --git a/frontend/src/Search/SearchIndexConnector.js b/frontend/src/Search/SearchIndexConnector.js
index 78a9866b2..e3302e73c 100644
--- a/frontend/src/Search/SearchIndexConnector.js
+++ b/frontend/src/Search/SearchIndexConnector.js
@@ -4,7 +4,6 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import withScrollPosition from 'Components/withScrollPosition';
import { bulkGrabReleases, cancelFetchReleases, clearReleases, fetchReleases, setReleasesFilter, setReleasesSort, setReleasesTableOption } from 'Store/Actions/releaseActions';
-import { fetchDownloadClients } from 'Store/Actions/Settings/downloadClients';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import createReleaseClientSideCollectionItemsSelector from 'Store/Selectors/createReleaseClientSideCollectionItemsSelector';
import SearchIndex from './SearchIndex';
@@ -56,20 +55,12 @@ function createMapDispatchToProps(dispatch, props) {
dispatchClearReleases() {
dispatch(clearReleases());
- },
-
- dispatchFetchDownloadClients() {
- dispatch(fetchDownloadClients());
}
};
}
class SearchIndexConnector extends Component {
- componentDidMount() {
- this.props.dispatchFetchDownloadClients();
- }
-
componentWillUnmount() {
this.props.dispatchCancelFetchReleases();
this.props.dispatchClearReleases();
@@ -94,7 +85,6 @@ SearchIndexConnector.propTypes = {
onBulkGrabPress: PropTypes.func.isRequired,
dispatchCancelFetchReleases: PropTypes.func.isRequired,
dispatchClearReleases: PropTypes.func.isRequired,
- dispatchFetchDownloadClients: PropTypes.func.isRequired,
items: PropTypes.arrayOf(PropTypes.object)
};
diff --git a/frontend/src/Search/Table/CategoryLabel.js b/frontend/src/Search/Table/CategoryLabel.js
new file mode 100644
index 000000000..5c076c521
--- /dev/null
+++ b/frontend/src/Search/Table/CategoryLabel.js
@@ -0,0 +1,43 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import Label from 'Components/Label';
+import Tooltip from 'Components/Tooltip/Tooltip';
+import { kinds, tooltipPositions } from 'Helpers/Props';
+
+function CategoryLabel({ categories }) {
+ const sortedCategories = categories.filter((cat) => cat.name !== undefined).sort((c) => c.id);
+
+ if (categories?.length === 0) {
+ return (
+ Unknown}
+ tooltip="Please report this issue to the GitHub as this shouldn't be happening"
+ position={tooltipPositions.LEFT}
+ />
+ );
+ }
+
+ return (
+
+ {
+ sortedCategories.map((category) => {
+ return (
+
+ );
+ })
+ }
+
+ );
+}
+
+CategoryLabel.defaultProps = {
+ categories: []
+};
+
+CategoryLabel.propTypes = {
+ categories: PropTypes.arrayOf(PropTypes.object).isRequired
+};
+
+export default CategoryLabel;
diff --git a/frontend/src/Search/Table/CategoryLabel.tsx b/frontend/src/Search/Table/CategoryLabel.tsx
deleted file mode 100644
index 4cfdeb1b2..000000000
--- a/frontend/src/Search/Table/CategoryLabel.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import React from 'react';
-import Label from 'Components/Label';
-import Tooltip from 'Components/Tooltip/Tooltip';
-import { kinds, tooltipPositions } from 'Helpers/Props';
-import { IndexerCategory } from 'Indexer/Indexer';
-import translate from 'Utilities/String/translate';
-
-interface CategoryLabelProps {
- categories: IndexerCategory[];
-}
-
-function CategoryLabel({ categories = [] }: CategoryLabelProps) {
- if (categories?.length === 0) {
- return (
- {translate('Unknown')}}
- tooltip="Please report this issue to the GitHub as this shouldn't be happening"
- position={tooltipPositions.LEFT}
- />
- );
- }
-
- const sortedCategories = categories
- .filter((cat) => cat.name !== undefined)
- .sort((a, b) => a.id - b.id);
-
- return (
-
- {sortedCategories.map((category) => {
- return ;
- })}
-
- );
-}
-
-export default CategoryLabel;
diff --git a/frontend/src/Search/Table/ReleaseLinks.css b/frontend/src/Search/Table/ReleaseLinks.css
deleted file mode 100644
index d37a082a1..000000000
--- a/frontend/src/Search/Table/ReleaseLinks.css
+++ /dev/null
@@ -1,13 +0,0 @@
-.links {
- margin: 0;
-}
-
-.link {
- white-space: nowrap;
-}
-
-.linkLabel {
- composes: label from '~Components/Label.css';
-
- cursor: pointer;
-}
diff --git a/frontend/src/Search/Table/ReleaseLinks.css.d.ts b/frontend/src/Search/Table/ReleaseLinks.css.d.ts
deleted file mode 100644
index 9f91f93a4..000000000
--- a/frontend/src/Search/Table/ReleaseLinks.css.d.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-// This file is automatically generated.
-// Please do not change this file!
-interface CssExports {
- 'link': string;
- 'linkLabel': string;
- 'links': string;
-}
-export const cssExports: CssExports;
-export default cssExports;
diff --git a/frontend/src/Search/Table/ReleaseLinks.tsx b/frontend/src/Search/Table/ReleaseLinks.tsx
deleted file mode 100644
index 38260bc21..000000000
--- a/frontend/src/Search/Table/ReleaseLinks.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import React from 'react';
-import Label from 'Components/Label';
-import Link from 'Components/Link/Link';
-import { kinds, sizes } from 'Helpers/Props';
-import { IndexerCategory } from 'Indexer/Indexer';
-import styles from './ReleaseLinks.css';
-
-interface ReleaseLinksProps {
- categories: IndexerCategory[];
- imdbId?: string;
- tmdbId?: number;
- tvdbId?: number;
- tvMazeId?: number;
-}
-
-function ReleaseLinks(props: ReleaseLinksProps) {
- const { categories = [], imdbId, tmdbId, tvdbId, tvMazeId } = props;
-
- const categoryNames = categories
- .filter((item) => item.id < 100000)
- .map((c) => c.name);
-
- return (
-
- {imdbId ? (
-
-
-
- ) : null}
-
- {tmdbId ? (
-
-
-
- ) : null}
-
- {tvdbId ? (
-
-
-
- ) : null}
-
- {tvMazeId ? (
-
-
-
- ) : null}
-
- );
-}
-
-export default ReleaseLinks;
diff --git a/frontend/src/Search/Table/SearchIndexItemConnector.js b/frontend/src/Search/Table/SearchIndexItemConnector.js
index 4cc7fb20c..490214529 100644
--- a/frontend/src/Search/Table/SearchIndexItemConnector.js
+++ b/frontend/src/Search/Table/SearchIndexItemConnector.js
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
+import { executeCommand } from 'Store/Actions/commandActions';
function createReleaseSelector() {
return createSelector(
@@ -36,6 +37,10 @@ function createMapStateToProps() {
);
}
+const mapDispatchToProps = {
+ dispatchExecuteCommand: executeCommand
+};
+
class SearchIndexItemConnector extends Component {
//
@@ -66,4 +71,4 @@ SearchIndexItemConnector.propTypes = {
component: PropTypes.elementType.isRequired
};
-export default connect(createMapStateToProps, null)(SearchIndexItemConnector);
+export default connect(createMapStateToProps, mapDispatchToProps)(SearchIndexItemConnector);
diff --git a/frontend/src/Search/Table/SearchIndexRow.css b/frontend/src/Search/Table/SearchIndexRow.css
index b36ec4071..342092b81 100644
--- a/frontend/src/Search/Table/SearchIndexRow.css
+++ b/frontend/src/Search/Table/SearchIndexRow.css
@@ -63,37 +63,7 @@
}
.externalLinks {
- composes: button from '~Components/Link/IconButton.css';
-
- color: var(--textColor);
-}
-
-.manualDownloadContent {
- position: relative;
- display: inline-block;
margin: 0 2px;
width: 22px;
- height: 20.39px;
- vertical-align: middle;
- line-height: 20.39px;
-
- &:hover {
- color: var(--iconButtonHoverColor);
- }
-}
-
-.interactiveIcon {
- position: absolute;
- top: 4px;
- left: 0;
- /* width: 100%; */
- text-align: center;
-}
-
-.downloadIcon {
- position: absolute;
- top: 7px;
- left: 8px;
- /* width: 100%; */
text-align: center;
}
diff --git a/frontend/src/Search/Table/SearchIndexRow.css.d.ts b/frontend/src/Search/Table/SearchIndexRow.css.d.ts
index 7552b96f9..6d625f58a 100644
--- a/frontend/src/Search/Table/SearchIndexRow.css.d.ts
+++ b/frontend/src/Search/Table/SearchIndexRow.css.d.ts
@@ -6,15 +6,12 @@ interface CssExports {
'category': string;
'cell': string;
'checkInput': string;
- 'downloadIcon': string;
'downloadLink': string;
'externalLinks': string;
'files': string;
'grabs': string;
'indexer': string;
'indexerFlags': string;
- 'interactiveIcon': string;
- 'manualDownloadContent': string;
'peers': string;
'protocol': string;
'size': string;
diff --git a/frontend/src/Search/Table/SearchIndexRow.js b/frontend/src/Search/Table/SearchIndexRow.js
new file mode 100644
index 000000000..67c267696
--- /dev/null
+++ b/frontend/src/Search/Table/SearchIndexRow.js
@@ -0,0 +1,396 @@
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import Icon from 'Components/Icon';
+import IconButton from 'Components/Link/IconButton';
+import Link from 'Components/Link/Link';
+import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
+import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
+import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
+import Popover from 'Components/Tooltip/Popover';
+import { icons, kinds, tooltipPositions } from 'Helpers/Props';
+import ProtocolLabel from 'Indexer/Index/Table/ProtocolLabel';
+import formatDateTime from 'Utilities/Date/formatDateTime';
+import formatAge from 'Utilities/Number/formatAge';
+import formatBytes from 'Utilities/Number/formatBytes';
+import titleCase from 'Utilities/String/titleCase';
+import translate from 'Utilities/String/translate';
+import CategoryLabel from './CategoryLabel';
+import Peers from './Peers';
+import styles from './SearchIndexRow.css';
+
+function getDownloadIcon(isGrabbing, isGrabbed, grabError) {
+ if (isGrabbing) {
+ return icons.SPINNER;
+ } else if (isGrabbed) {
+ return icons.DOWNLOADING;
+ } else if (grabError) {
+ return icons.DOWNLOADING;
+ }
+
+ return icons.DOWNLOAD;
+}
+
+function getDownloadKind(isGrabbed, grabError) {
+ if (isGrabbed) {
+ return kinds.SUCCESS;
+ }
+
+ if (grabError) {
+ return kinds.DANGER;
+ }
+
+ return kinds.DEFAULT;
+}
+
+function getDownloadTooltip(isGrabbing, isGrabbed, grabError) {
+ if (isGrabbing) {
+ return '';
+ } else if (isGrabbed) {
+ return translate('AddedToDownloadClient');
+ } else if (grabError) {
+ return grabError;
+ }
+
+ return translate('AddToDownloadClient');
+}
+
+class SearchIndexRow extends Component {
+
+ //
+ // Lifecycle
+
+ constructor(props, context) {
+ super(props, context);
+
+ this.state = {
+ isConfirmGrabModalOpen: false
+ };
+ }
+
+ //
+ // Listeners
+
+ onGrabPress = () => {
+ const {
+ guid,
+ indexerId,
+ onGrabPress
+ } = this.props;
+
+ onGrabPress({
+ guid,
+ indexerId
+ });
+ };
+
+ onSavePress = () => {
+ const {
+ downloadUrl,
+ fileName,
+ onSavePress
+ } = this.props;
+
+ onSavePress({
+ downloadUrl,
+ fileName
+ });
+ };
+
+ //
+ // Render
+
+ render() {
+ const {
+ guid,
+ protocol,
+ downloadUrl,
+ magnetUrl,
+ categories,
+ age,
+ ageHours,
+ ageMinutes,
+ publishDate,
+ title,
+ infoUrl,
+ indexer,
+ size,
+ files,
+ grabs,
+ seeders,
+ leechers,
+ indexerFlags,
+ columns,
+ isGrabbing,
+ isGrabbed,
+ grabError,
+ longDateFormat,
+ timeFormat,
+ isSelected,
+ onSelectedChange
+ } = this.props;
+
+ return (
+ <>
+ {
+ columns.map((column) => {
+ const {
+ isVisible
+ } = column;
+
+ if (!isVisible) {
+ return null;
+ }
+
+ if (column.name === 'select') {
+ return (
+
+ );
+ }
+
+ if (column.name === 'protocol') {
+ return (
+
+
+
+ );
+ }
+
+ if (column.name === 'age') {
+ return (
+
+ {formatAge(age, ageHours, ageMinutes)}
+
+ );
+ }
+
+ if (column.name === 'sortTitle') {
+ return (
+
+
+
+ {title}
+
+
+
+ );
+ }
+
+ if (column.name === 'indexer') {
+ return (
+
+ {indexer}
+
+ );
+ }
+
+ if (column.name === 'size') {
+ return (
+
+ {formatBytes(size)}
+
+ );
+ }
+
+ if (column.name === 'files') {
+ return (
+
+ {files}
+
+ );
+ }
+
+ if (column.name === 'grabs') {
+ return (
+
+ {grabs}
+
+ );
+ }
+
+ if (column.name === 'peers') {
+ return (
+
+ {
+ protocol === 'torrent' &&
+
+ }
+
+ );
+ }
+
+ if (column.name === 'category') {
+ return (
+
+
+
+ );
+ }
+
+ if (column.name === 'indexerFlags') {
+ return (
+
+ {
+ !!indexerFlags.length &&
+
+ }
+ title={translate('IndexerFlags')}
+ body={
+
+ {
+ indexerFlags.map((flag, index) => {
+ return (
+ -
+ {titleCase(flag)}
+
+ );
+ })
+ }
+
+ }
+ position={tooltipPositions.LEFT}
+ />
+ }
+
+ );
+ }
+
+ if (column.name === 'actions') {
+ return (
+
+
+
+ {
+ downloadUrl ?
+ :
+ null
+ }
+
+ {
+ magnetUrl ?
+ :
+ null
+ }
+
+ );
+ }
+
+ return null;
+ })
+ }
+ >
+ );
+ }
+}
+
+SearchIndexRow.propTypes = {
+ guid: PropTypes.string.isRequired,
+ categories: PropTypes.arrayOf(PropTypes.object).isRequired,
+ protocol: PropTypes.string.isRequired,
+ age: PropTypes.number.isRequired,
+ ageHours: PropTypes.number.isRequired,
+ ageMinutes: PropTypes.number.isRequired,
+ publishDate: PropTypes.string.isRequired,
+ title: PropTypes.string.isRequired,
+ fileName: PropTypes.string.isRequired,
+ infoUrl: PropTypes.string.isRequired,
+ downloadUrl: PropTypes.string,
+ magnetUrl: PropTypes.string,
+ indexerId: PropTypes.number.isRequired,
+ indexer: PropTypes.string.isRequired,
+ size: PropTypes.number.isRequired,
+ files: PropTypes.number,
+ grabs: PropTypes.number,
+ seeders: PropTypes.number,
+ leechers: PropTypes.number,
+ indexerFlags: PropTypes.arrayOf(PropTypes.string).isRequired,
+ columns: PropTypes.arrayOf(PropTypes.object).isRequired,
+ onGrabPress: PropTypes.func.isRequired,
+ onSavePress: PropTypes.func.isRequired,
+ isGrabbing: PropTypes.bool.isRequired,
+ isGrabbed: PropTypes.bool.isRequired,
+ grabError: PropTypes.string,
+ longDateFormat: PropTypes.string.isRequired,
+ timeFormat: PropTypes.string.isRequired,
+ isSelected: PropTypes.bool,
+ onSelectedChange: PropTypes.func.isRequired
+};
+
+SearchIndexRow.defaultProps = {
+ isGrabbing: false,
+ isGrabbed: false
+};
+
+export default SearchIndexRow;
diff --git a/frontend/src/Search/Table/SearchIndexRow.tsx b/frontend/src/Search/Table/SearchIndexRow.tsx
deleted file mode 100644
index 1136a7f64..000000000
--- a/frontend/src/Search/Table/SearchIndexRow.tsx
+++ /dev/null
@@ -1,395 +0,0 @@
-import React, { useCallback, useState } from 'react';
-import { useSelector } from 'react-redux';
-import Icon from 'Components/Icon';
-import IconButton from 'Components/Link/IconButton';
-import Link from 'Components/Link/Link';
-import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
-import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
-import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
-import Column from 'Components/Table/Column';
-import Popover from 'Components/Tooltip/Popover';
-import DownloadProtocol from 'DownloadClient/DownloadProtocol';
-import { icons, kinds, tooltipPositions } from 'Helpers/Props';
-import ProtocolLabel from 'Indexer/Index/Table/ProtocolLabel';
-import { IndexerCategory } from 'Indexer/Indexer';
-import OverrideMatchModal from 'Search/OverrideMatch/OverrideMatchModal';
-import createEnabledDownloadClientsSelector from 'Store/Selectors/createEnabledDownloadClientsSelector';
-import { SelectStateInputProps } from 'typings/props';
-import formatDateTime from 'Utilities/Date/formatDateTime';
-import formatAge from 'Utilities/Number/formatAge';
-import formatBytes from 'Utilities/Number/formatBytes';
-import titleCase from 'Utilities/String/titleCase';
-import translate from 'Utilities/String/translate';
-import CategoryLabel from './CategoryLabel';
-import Peers from './Peers';
-import ReleaseLinks from './ReleaseLinks';
-import styles from './SearchIndexRow.css';
-
-function getDownloadIcon(
- isGrabbing: boolean,
- isGrabbed: boolean,
- grabError?: string
-) {
- if (isGrabbing) {
- return icons.SPINNER;
- } else if (isGrabbed) {
- return icons.DOWNLOADING;
- } else if (grabError) {
- return icons.DOWNLOADING;
- }
-
- return icons.DOWNLOAD;
-}
-
-function getDownloadKind(isGrabbed: boolean, grabError?: string) {
- if (isGrabbed) {
- return kinds.SUCCESS;
- }
-
- if (grabError) {
- return kinds.DANGER;
- }
-
- return kinds.DEFAULT;
-}
-
-function getDownloadTooltip(
- isGrabbing: boolean,
- isGrabbed: boolean,
- grabError?: string
-) {
- if (isGrabbing) {
- return '';
- } else if (isGrabbed) {
- return translate('AddedToDownloadClient');
- } else if (grabError) {
- return grabError;
- }
-
- return translate('AddToDownloadClient');
-}
-
-interface SearchIndexRowProps {
- guid: string;
- protocol: DownloadProtocol;
- age: number;
- ageHours: number;
- ageMinutes: number;
- publishDate: string;
- title: string;
- fileName: string;
- infoUrl: string;
- downloadUrl?: string;
- magnetUrl?: string;
- indexerId: number;
- indexer: string;
- categories: IndexerCategory[];
- size: number;
- files?: number;
- grabs?: number;
- seeders?: number;
- leechers?: number;
- imdbId?: string;
- tmdbId?: number;
- tvdbId?: number;
- tvMazeId?: number;
- indexerFlags: string[];
- isGrabbing: boolean;
- isGrabbed: boolean;
- grabError?: string;
- longDateFormat: string;
- timeFormat: string;
- columns: Column[];
- isSelected?: boolean;
- onSelectedChange(result: SelectStateInputProps): void;
- onGrabPress(...args: unknown[]): void;
- onSavePress(...args: unknown[]): void;
-}
-
-function SearchIndexRow(props: SearchIndexRowProps) {
- const {
- guid,
- indexerId,
- protocol,
- categories,
- age,
- ageHours,
- ageMinutes,
- publishDate,
- title,
- fileName,
- infoUrl,
- downloadUrl,
- magnetUrl,
- indexer,
- size,
- files,
- grabs,
- seeders,
- leechers,
- imdbId,
- tmdbId,
- tvdbId,
- tvMazeId,
- indexerFlags = [],
- isGrabbing = false,
- isGrabbed = false,
- grabError,
- longDateFormat,
- timeFormat,
- columns,
- isSelected,
- onSelectedChange,
- onGrabPress,
- onSavePress,
- } = props;
-
- const [isOverrideModalOpen, setIsOverrideModalOpen] = useState(false);
-
- const { items: downloadClients } = useSelector(
- createEnabledDownloadClientsSelector(protocol)
- );
-
- const onGrabPressWrapper = useCallback(() => {
- onGrabPress({
- guid,
- indexerId,
- });
- }, [guid, indexerId, onGrabPress]);
-
- const onSavePressWrapper = useCallback(() => {
- onSavePress({
- downloadUrl,
- fileName,
- });
- }, [downloadUrl, fileName, onSavePress]);
-
- const onOverridePress = useCallback(() => {
- setIsOverrideModalOpen(true);
- }, [setIsOverrideModalOpen]);
-
- const onOverrideModalClose = useCallback(() => {
- setIsOverrideModalOpen(false);
- }, [setIsOverrideModalOpen]);
-
- return (
- <>
- {columns.map((column) => {
- const { name, isVisible } = column;
-
- if (!isVisible) {
- return null;
- }
-
- if (name === 'select') {
- return (
-
- );
- }
-
- if (name === 'protocol') {
- return (
-
-
-
- );
- }
-
- if (name === 'age') {
- return (
-
- {formatAge(age, ageHours, ageMinutes)}
-
- );
- }
-
- if (name === 'sortTitle') {
- return (
-
-
- {title}
-
-
- );
- }
-
- if (name === 'indexer') {
- return (
-
- {indexer}
-
- );
- }
-
- if (name === 'size') {
- return (
-
- {formatBytes(size)}
-
- );
- }
-
- if (name === 'files') {
- return (
-
- {files}
-
- );
- }
-
- if (name === 'grabs') {
- return (
-
- {grabs}
-
- );
- }
-
- if (name === 'peers') {
- return (
-
- {protocol === 'torrent' && (
-
- )}
-
- );
- }
-
- if (name === 'category') {
- return (
-
-
-
- );
- }
-
- if (name === 'indexerFlags') {
- return (
-
- {!!indexerFlags.length && (
- }
- title={translate('IndexerFlags')}
- body={
-
- {indexerFlags.map((flag, index) => {
- return - {titleCase(flag)}
;
- })}
-
- }
- position={tooltipPositions.LEFT}
- />
- )}
-
- );
- }
-
- if (name === 'actions') {
- return (
-
-
-
- {downloadClients.length > 1 ? (
-
-
-
-
-
-
-
- ) : null}
-
- {downloadUrl ? (
-
- ) : null}
-
- {magnetUrl ? (
-
- ) : null}
-
- {imdbId || tmdbId || tvdbId || tvMazeId ? (
-
- }
- title={translate('Links')}
- body={
-
- }
- position={tooltipPositions.TOP}
- />
- ) : null}
-
- );
- }
-
- return null;
- })}
-
-
- >
- );
-}
-
-export default SearchIndexRow;
diff --git a/frontend/src/Settings/Applications/ApplicationSettings.tsx b/frontend/src/Settings/Applications/ApplicationSettings.tsx
index 7fc4b1d7b..c35d55e2d 100644
--- a/frontend/src/Settings/Applications/ApplicationSettings.tsx
+++ b/frontend/src/Settings/Applications/ApplicationSettings.tsx
@@ -1,4 +1,4 @@
-import React, { useCallback, useState } from 'react';
+import React, { Fragment, useCallback, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import AppState from 'App/State/AppState';
import { APP_INDEXER_SYNC } from 'Commands/commandNames';
@@ -56,7 +56,7 @@ function ApplicationSettings() {
// @ts-ignore
showSave={false}
additionalButtons={
- <>
+
- >
+
}
/>
diff --git a/frontend/src/Settings/Applications/Applications/Application.js b/frontend/src/Settings/Applications/Applications/Application.js
index 086d39ee1..610cc344d 100644
--- a/frontend/src/Settings/Applications/Applications/Application.js
+++ b/frontend/src/Settings/Applications/Applications/Application.js
@@ -57,7 +57,6 @@ class Application extends Component {
const {
id,
name,
- enable,
syncLevel,
fields,
tags,
@@ -78,7 +77,7 @@ class Application extends Component {
{
- enable && applicationUrl ?
+ applicationUrl ?