- {translate('Label')}
+ Label
diff --git a/frontend/src/Components/Filter/Builder/FilterBuilderRow.js b/frontend/src/Components/Filter/Builder/FilterBuilderRow.js
index b02844c61..ed375b745 100644
--- a/frontend/src/Components/Filter/Builder/FilterBuilderRow.js
+++ b/frontend/src/Components/Filter/Builder/FilterBuilderRow.js
@@ -3,13 +3,10 @@ import React, { Component } from 'react';
import SelectInput from 'Components/Form/SelectInput';
import IconButton from 'Components/Link/IconButton';
import { filterBuilderTypes, filterBuilderValueTypes, icons } from 'Helpers/Props';
-import sortByProp from 'Utilities/Array/sortByProp';
import AppProfileFilterBuilderRowValueConnector from './AppProfileFilterBuilderRowValueConnector';
import BoolFilterBuilderRowValue from './BoolFilterBuilderRowValue';
-import CategoryFilterBuilderRowValue from './CategoryFilterBuilderRowValue';
import DateFilterBuilderRowValue from './DateFilterBuilderRowValue';
import FilterBuilderRowValueConnector from './FilterBuilderRowValueConnector';
-import HistoryEventTypeFilterBuilderRowValue from './HistoryEventTypeFilterBuilderRowValue';
import IndexerFilterBuilderRowValueConnector from './IndexerFilterBuilderRowValueConnector';
import PrivacyFilterBuilderRowValue from './PrivacyFilterBuilderRowValue';
import ProtocolFilterBuilderRowValue from './ProtocolFilterBuilderRowValue';
@@ -58,15 +55,9 @@ function getRowValueConnector(selectedFilterBuilderProp) {
case filterBuilderValueTypes.BOOL:
return BoolFilterBuilderRowValue;
- case filterBuilderValueTypes.CATEGORY:
- return CategoryFilterBuilderRowValue;
-
case filterBuilderValueTypes.DATE:
return DateFilterBuilderRowValue;
- case filterBuilderValueTypes.HISTORY_EVENT_TYPE:
- return HistoryEventTypeFilterBuilderRowValue;
-
case filterBuilderValueTypes.INDEXER:
return IndexerFilterBuilderRowValueConnector;
@@ -207,13 +198,11 @@ class FilterBuilderRow extends Component {
const selectedFilterBuilderProp = this.selectedFilterBuilderProp;
const keyOptions = filterBuilderProps.map((availablePropFilter) => {
- const { name, label } = availablePropFilter;
-
return {
- key: name,
- value: typeof label === 'function' ? label() : label
+ key: availablePropFilter.name,
+ value: availablePropFilter.label
};
- }).sort(sortByProp('value'));
+ });
const ValueComponent = getRowValueConnector(selectedFilterBuilderProp);
diff --git a/frontend/src/Components/Filter/Builder/FilterBuilderRowValueConnector.js b/frontend/src/Components/Filter/Builder/FilterBuilderRowValueConnector.js
index d1419327a..a7aed80b6 100644
--- a/frontend/src/Components/Filter/Builder/FilterBuilderRowValueConnector.js
+++ b/frontend/src/Components/Filter/Builder/FilterBuilderRowValueConnector.js
@@ -3,7 +3,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { filterBuilderTypes } from 'Helpers/Props';
import * as filterTypes from 'Helpers/Props/filterTypes';
-import sortByProp from 'Utilities/Array/sortByProp';
+import sortByName from 'Utilities/Array/sortByName';
import FilterBuilderRowValue from './FilterBuilderRowValue';
function createTagListSelector() {
@@ -38,7 +38,7 @@ function createTagListSelector() {
}
return acc;
- }, []).sort(sortByProp('name'));
+ }, []).sort(sortByName);
}
return _.uniqBy(items, 'id');
diff --git a/frontend/src/Components/Filter/Builder/FilterBuilderRowValueProps.ts b/frontend/src/Components/Filter/Builder/FilterBuilderRowValueProps.ts
deleted file mode 100644
index 5bf9e5785..000000000
--- a/frontend/src/Components/Filter/Builder/FilterBuilderRowValueProps.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { FilterBuilderProp } from 'App/State/AppState';
-
-interface FilterBuilderRowOnChangeProps {
- name: string;
- value: unknown[];
-}
-
-interface FilterBuilderRowValueProps {
- filterType?: string;
- filterValue: string | number | object | string[] | number[] | object[];
- selectedFilterBuilderProp: FilterBuilderProp
;
- sectionItem: unknown[];
- onChange: (payload: FilterBuilderRowOnChangeProps) => void;
-}
-
-export default FilterBuilderRowValueProps;
diff --git a/frontend/src/Components/Filter/Builder/HistoryEventTypeFilterBuilderRowValue.tsx b/frontend/src/Components/Filter/Builder/HistoryEventTypeFilterBuilderRowValue.tsx
deleted file mode 100644
index 03c5f7227..000000000
--- a/frontend/src/Components/Filter/Builder/HistoryEventTypeFilterBuilderRowValue.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import React from 'react';
-import translate from 'Utilities/String/translate';
-import FilterBuilderRowValue from './FilterBuilderRowValue';
-import FilterBuilderRowValueProps from './FilterBuilderRowValueProps';
-
-const EVENT_TYPE_OPTIONS = [
- {
- id: 1,
- get name() {
- return translate('Grabbed');
- },
- },
- {
- id: 3,
- get name() {
- return translate('IndexerRss');
- },
- },
- {
- id: 2,
- get name() {
- return translate('IndexerQuery');
- },
- },
- {
- id: 4,
- get name() {
- return translate('IndexerAuth');
- },
- },
-];
-
-function HistoryEventTypeFilterBuilderRowValue(
- props: FilterBuilderRowValueProps
-) {
- return ;
-}
-
-export default HistoryEventTypeFilterBuilderRowValue;
diff --git a/frontend/src/Components/Filter/Builder/PrivacyFilterBuilderRowValue.js b/frontend/src/Components/Filter/Builder/PrivacyFilterBuilderRowValue.js
index 4f6250151..4004f0ced 100644
--- a/frontend/src/Components/Filter/Builder/PrivacyFilterBuilderRowValue.js
+++ b/frontend/src/Components/Filter/Builder/PrivacyFilterBuilderRowValue.js
@@ -3,24 +3,9 @@ import translate from 'Utilities/String/translate';
import FilterBuilderRowValue from './FilterBuilderRowValue';
const privacyTypes = [
- {
- id: 'public',
- get name() {
- return translate('Public');
- }
- },
- {
- id: 'private',
- get name() {
- return translate('Private');
- }
- },
- {
- id: 'semiPrivate',
- get name() {
- return translate('SemiPrivate');
- }
- }
+ { id: 'public', name: translate('Public') },
+ { id: 'private', name: translate('Private') },
+ { id: 'semiPrivate', name: translate('SemiPrivate') }
];
function PrivacyFilterBuilderRowValue(props) {
diff --git a/frontend/src/Components/Filter/CustomFilters/CustomFilter.js b/frontend/src/Components/Filter/CustomFilters/CustomFilter.js
index 9f378d5a2..7407f729a 100644
--- a/frontend/src/Components/Filter/CustomFilters/CustomFilter.js
+++ b/frontend/src/Components/Filter/CustomFilters/CustomFilter.js
@@ -37,8 +37,8 @@ class CustomFilter extends Component {
dispatchSetFilter
} = this.props;
- // Assume that delete and then unmounting means the deletion was successful.
- // Moving this check to an ancestor would be more accurate, but would have
+ // Assume that delete and then unmounting means the delete was successful.
+ // Moving this check to a ancestor would be more accurate, but would have
// more boilerplate.
if (this.state.isDeleting && id === selectedFilterKey) {
dispatchSetFilter({ selectedFilterKey: 'all' });
diff --git a/frontend/src/Components/Filter/CustomFilters/CustomFiltersModalContent.js b/frontend/src/Components/Filter/CustomFilters/CustomFiltersModalContent.js
index 99cb6ec5c..07660426e 100644
--- a/frontend/src/Components/Filter/CustomFilters/CustomFiltersModalContent.js
+++ b/frontend/src/Components/Filter/CustomFilters/CustomFiltersModalContent.js
@@ -5,7 +5,6 @@ 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 sortByProp from 'Utilities/Array/sortByProp';
import translate from 'Utilities/String/translate';
import CustomFilter from './CustomFilter';
import styles from './CustomFiltersModalContent.css';
@@ -31,24 +30,22 @@ function CustomFiltersModalContent(props) {
{
- customFilters
- .sort((a, b) => sortByProp(a, b, 'label'))
- .map((customFilter) => {
- return (
-
- );
- })
+ customFilters.map((customFilter) => {
+ return (
+
+ );
+ })
}
diff --git a/frontend/src/Components/Form/AppProfileSelectInputConnector.js b/frontend/src/Components/Form/AppProfileSelectInputConnector.js
index 0ab181e2f..1aef10c30 100644
--- a/frontend/src/Components/Form/AppProfileSelectInputConnector.js
+++ b/frontend/src/Components/Form/AppProfileSelectInputConnector.js
@@ -4,13 +4,13 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
-import sortByProp from 'Utilities/Array/sortByProp';
+import sortByName from 'Utilities/Array/sortByName';
import translate from 'Utilities/String/translate';
import SelectInput from './SelectInput';
function createMapStateToProps() {
return createSelector(
- createSortedSectionSelector('settings.appProfiles', sortByProp('name')),
+ createSortedSectionSelector('settings.appProfiles', sortByName),
(state, { includeNoChange }) => includeNoChange,
(state, { includeMixed }) => includeMixed,
(appProfiles, includeNoChange, includeMixed) => {
@@ -24,20 +24,16 @@ function createMapStateToProps() {
if (includeNoChange) {
values.unshift({
key: 'noChange',
- get value() {
- return translate('NoChange');
- },
- isDisabled: true
+ value: translate('NoChange'),
+ disabled: true
});
}
if (includeMixed) {
values.unshift({
key: 'mixed',
- get value() {
- return `(${translate('Mixed')})`;
- },
- isDisabled: true
+ value: '(Mixed)',
+ disabled: true
});
}
diff --git a/frontend/src/Components/Form/AvailabilitySelectInput.js b/frontend/src/Components/Form/AvailabilitySelectInput.js
new file mode 100644
index 000000000..af9bdb2d6
--- /dev/null
+++ b/frontend/src/Components/Form/AvailabilitySelectInput.js
@@ -0,0 +1,54 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import SelectInput from './SelectInput';
+
+const availabilityOptions = [
+ { key: 'announced', value: 'Announced' },
+ { key: 'inCinemas', value: 'In Cinemas' },
+ { key: 'released', value: 'Released' },
+ { key: 'preDB', value: 'PreDB' }
+];
+
+function AvailabilitySelectInput(props) {
+ const values = [...availabilityOptions];
+
+ const {
+ includeNoChange,
+ includeMixed
+ } = props;
+
+ if (includeNoChange) {
+ values.unshift({
+ key: 'noChange',
+ value: 'No Change',
+ disabled: true
+ });
+ }
+
+ if (includeMixed) {
+ values.unshift({
+ key: 'mixed',
+ value: '(Mixed)',
+ disabled: true
+ });
+ }
+
+ return (
+
+ );
+}
+
+AvailabilitySelectInput.propTypes = {
+ includeNoChange: PropTypes.bool.isRequired,
+ includeMixed: PropTypes.bool.isRequired
+};
+
+AvailabilitySelectInput.defaultProps = {
+ includeNoChange: false,
+ includeMixed: false
+};
+
+export default AvailabilitySelectInput;
diff --git a/frontend/src/Components/Form/DownloadClientSelectInputConnector.js b/frontend/src/Components/Form/DownloadClientSelectInputConnector.js
index 9cf7a429a..162c79885 100644
--- a/frontend/src/Components/Form/DownloadClientSelectInputConnector.js
+++ b/frontend/src/Components/Form/DownloadClientSelectInputConnector.js
@@ -3,8 +3,7 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchDownloadClients } from 'Store/Actions/settingsActions';
-import sortByProp from 'Utilities/Array/sortByProp';
-import translate from 'Utilities/String/translate';
+import sortByName from 'Utilities/Array/sortByName';
import EnhancedSelectInput from './EnhancedSelectInput';
function createMapStateToProps() {
@@ -22,17 +21,16 @@ function createMapStateToProps() {
const values = items
.filter((downloadClient) => downloadClient.protocol === protocolFilter)
- .sort(sortByProp('name'))
+ .sort(sortByName)
.map((downloadClient) => ({
key: downloadClient.id,
- value: downloadClient.name,
- hint: `(${downloadClient.id})`
+ value: downloadClient.name
}));
if (includeAny) {
values.unshift({
key: 0,
- value: `(${translate('Any')})`
+ value: '(Any)'
});
}
diff --git a/frontend/src/Components/Form/EnhancedSelectInput.js b/frontend/src/Components/Form/EnhancedSelectInput.js
index 79b1c999c..cc4215025 100644
--- a/frontend/src/Components/Form/EnhancedSelectInput.js
+++ b/frontend/src/Components/Form/EnhancedSelectInput.js
@@ -20,8 +20,6 @@ import HintedSelectInputSelectedValue from './HintedSelectInputSelectedValue';
import TextInput from './TextInput';
import styles from './EnhancedSelectInput.css';
-const MINIMUM_DISTANCE_FROM_EDGE = 10;
-
function isArrowKey(keyCode) {
return keyCode === keyCodes.UP_ARROW || keyCode === keyCodes.DOWN_ARROW;
}
@@ -139,9 +137,18 @@ class EnhancedSelectInput extends Component {
// Listeners
onComputeMaxHeight = (data) => {
+ const {
+ top,
+ bottom
+ } = data.offsets.reference;
+
const windowHeight = window.innerHeight;
- data.styles.maxHeight = windowHeight - MINIMUM_DISTANCE_FROM_EDGE;
+ if ((/^botton/).test(data.placement)) {
+ data.styles.maxHeight = windowHeight - bottom;
+ } else {
+ data.styles.maxHeight = top;
+ }
return data;
};
@@ -264,29 +271,26 @@ class EnhancedSelectInput extends Component {
this.setState({ isOpen: !this.state.isOpen });
};
- onSelect = (newValue) => {
- const { name, value, values, onChange } = this.props;
-
- if (Array.isArray(value)) {
- let arrayValue = null;
- const index = value.indexOf(newValue);
-
+ onSelect = (value) => {
+ if (Array.isArray(this.props.value)) {
+ let newValue = null;
+ const index = this.props.value.indexOf(value);
if (index === -1) {
- arrayValue = values.map((v) => v.key).filter((v) => (v === newValue) || value.includes(v));
+ newValue = this.props.values.map((v) => v.key).filter((v) => (v === value) || this.props.value.includes(v));
} else {
- arrayValue = [...value];
- arrayValue.splice(index, 1);
+ newValue = [...this.props.value];
+ newValue.splice(index, 1);
}
- onChange({
- name,
- value: arrayValue
+ this.props.onChange({
+ name: this.props.name,
+ value: newValue
});
} else {
this.setState({ isOpen: false });
- onChange({
- name,
- value: newValue
+ this.props.onChange({
+ name: this.props.name,
+ value
});
}
};
@@ -453,10 +457,6 @@ class EnhancedSelectInput extends Component {
order: 851,
enabled: true,
fn: this.onComputeMaxHeight
- },
- preventOverflow: {
- enabled: true,
- boundariesElement: 'viewport'
}
}}
>
@@ -485,7 +485,7 @@ class EnhancedSelectInput extends Component {
values.map((v, index) => {
const hasParent = v.parentKey !== undefined;
const depth = hasParent ? 1 : 0;
- const parentSelected = hasParent && Array.isArray(value) && value.includes(v.parentKey);
+ const parentSelected = hasParent && value.includes(v.parentKey);
return (
{error.errorMessage}
-
- {
- error.detailedDescription ?
- }
- tooltip={error.detailedDescription}
- kind={kinds.INVERSE}
- position={tooltipPositions.TOP}
- /> :
- null
- }
);
})
@@ -53,18 +39,6 @@ function Form(props) {
kind={kinds.WARNING}
>
{warning.errorMessage}
-
- {
- warning.detailedDescription ?
- }
- tooltip={warning.detailedDescription}
- kind={kinds.INVERSE}
- position={tooltipPositions.TOP}
- /> :
- null
- }
);
})
diff --git a/frontend/src/Components/Form/FormInputButton.js b/frontend/src/Components/Form/FormInputButton.js
new file mode 100644
index 000000000..a7145363a
--- /dev/null
+++ b/frontend/src/Components/Form/FormInputButton.js
@@ -0,0 +1,54 @@
+import classNames from 'classnames';
+import PropTypes from 'prop-types';
+import React from 'react';
+import Button from 'Components/Link/Button';
+import SpinnerButton from 'Components/Link/SpinnerButton';
+import { kinds } from 'Helpers/Props';
+import styles from './FormInputButton.css';
+
+function FormInputButton(props) {
+ const {
+ className,
+ canSpin,
+ isLastButton,
+ ...otherProps
+ } = props;
+
+ if (canSpin) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+}
+
+FormInputButton.propTypes = {
+ className: PropTypes.string.isRequired,
+ isLastButton: PropTypes.bool.isRequired,
+ canSpin: PropTypes.bool.isRequired
+};
+
+FormInputButton.defaultProps = {
+ className: styles.button,
+ isLastButton: true,
+ canSpin: false
+};
+
+export default FormInputButton;
diff --git a/frontend/src/Components/Form/FormInputButton.tsx b/frontend/src/Components/Form/FormInputButton.tsx
deleted file mode 100644
index f61779122..000000000
--- a/frontend/src/Components/Form/FormInputButton.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import classNames from 'classnames';
-import React from 'react';
-import Button, { ButtonProps } from 'Components/Link/Button';
-import SpinnerButton from 'Components/Link/SpinnerButton';
-import { kinds } from 'Helpers/Props';
-import styles from './FormInputButton.css';
-
-export interface FormInputButtonProps extends ButtonProps {
- canSpin?: boolean;
- isLastButton?: boolean;
-}
-
-function FormInputButton({
- className = styles.button,
- canSpin = false,
- isLastButton = true,
- ...otherProps
-}: FormInputButtonProps) {
- if (canSpin) {
- return (
-
- );
- }
-
- return (
-
- );
-}
-
-export default FormInputButton;
diff --git a/frontend/src/Components/Form/FormInputGroup.js b/frontend/src/Components/Form/FormInputGroup.js
index 5b3b42de2..bd0e9184d 100644
--- a/frontend/src/Components/Form/FormInputGroup.js
+++ b/frontend/src/Components/Form/FormInputGroup.js
@@ -5,6 +5,7 @@ import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import AppProfileSelectInputConnector from './AppProfileSelectInputConnector';
import AutoCompleteInput from './AutoCompleteInput';
+import AvailabilitySelectInput from './AvailabilitySelectInput';
import CaptchaInputConnector from './CaptchaInputConnector';
import CardigannCaptchaInputConnector from './CardigannCaptchaInputConnector';
import CheckInput from './CheckInput';
@@ -36,6 +37,9 @@ function getComponent(type) {
case inputTypes.AUTO_COMPLETE:
return AutoCompleteInput;
+ case inputTypes.AVAILABILITY_SELECT:
+ return AvailabilitySelectInput;
+
case inputTypes.CAPTCHA:
return CaptchaInputConnector;
@@ -256,7 +260,6 @@ FormInputGroup.propTypes = {
name: PropTypes.string.isRequired,
value: PropTypes.any,
values: PropTypes.arrayOf(PropTypes.any),
- isFloat: PropTypes.bool,
type: PropTypes.string.isRequired,
kind: PropTypes.oneOf(kinds.all),
min: PropTypes.number,
@@ -267,7 +270,6 @@ FormInputGroup.propTypes = {
helpTexts: PropTypes.arrayOf(PropTypes.string),
helpTextWarning: PropTypes.string,
helpLink: PropTypes.string,
- autoFocus: PropTypes.bool,
includeNoChange: PropTypes.bool,
includeNoChangeDisabled: PropTypes.bool,
selectedValueOptions: PropTypes.object,
diff --git a/frontend/src/Components/Form/FormInputHelpText.js b/frontend/src/Components/Form/FormInputHelpText.js
index 00024684e..39a0a8e74 100644
--- a/frontend/src/Components/Form/FormInputHelpText.js
+++ b/frontend/src/Components/Form/FormInputHelpText.js
@@ -25,7 +25,7 @@ function FormInputHelpText(props) {
isCheckInput && styles.isCheckInput
)}
>
- {text}
+
{
link ?
diff --git a/frontend/src/Components/Form/FormLabel.css b/frontend/src/Components/Form/FormLabel.css
index 54a4678e8..074b6091d 100644
--- a/frontend/src/Components/Form/FormLabel.css
+++ b/frontend/src/Components/Form/FormLabel.css
@@ -2,10 +2,8 @@
display: flex;
justify-content: flex-end;
margin-right: $formLabelRightMarginWidth;
- padding-top: 8px;
- min-height: 35px;
- text-align: end;
font-weight: bold;
+ line-height: 35px;
}
.hasError {
diff --git a/frontend/src/Components/Form/HintedSelectInputOption.js b/frontend/src/Components/Form/HintedSelectInputOption.js
index 4f59fc0a4..4957ece2a 100644
--- a/frontend/src/Components/Form/HintedSelectInputOption.js
+++ b/frontend/src/Components/Form/HintedSelectInputOption.js
@@ -33,11 +33,11 @@ function HintedSelectInputOption(props) {
isMobile && styles.isMobile
)}
>
- {typeof value === 'function' ? value() : value}
+ {value}
{
hint != null &&
-
+
{hint}
}
@@ -48,7 +48,7 @@ function HintedSelectInputOption(props) {
HintedSelectInputOption.propTypes = {
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
- value: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
+ value: PropTypes.string.isRequired,
hint: PropTypes.node,
depth: PropTypes.number,
isSelected: PropTypes.bool.isRequired,
diff --git a/frontend/src/Components/Form/HintedSelectInputSelectedValue.js b/frontend/src/Components/Form/HintedSelectInputSelectedValue.js
index a3fecf324..07f6c9e25 100644
--- a/frontend/src/Components/Form/HintedSelectInputSelectedValue.js
+++ b/frontend/src/Components/Form/HintedSelectInputSelectedValue.js
@@ -24,7 +24,7 @@ function HintedSelectInputSelectedValue(props) {
>
{
- isMultiSelect ?
+ isMultiSelect &&
value.map((key, index) => {
const v = valuesMap[key];
return (
@@ -32,28 +32,26 @@ function HintedSelectInputSelectedValue(props) {
{v ? v.value : key}
);
- }) :
- null
+ })
}
{
- isMultiSelect ? null : value
+ !isMultiSelect && value
}
{
- hint != null && includeHint ?
+ hint != null && includeHint &&
{hint}
-
:
- null
+
}
);
}
HintedSelectInputSelectedValue.propTypes = {
- value: PropTypes.oneOfType([PropTypes.number, PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number]))]).isRequired,
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number]))]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
hint: PropTypes.string,
isMultiSelect: PropTypes.bool.isRequired,
diff --git a/frontend/src/Components/Form/IndexersSelectInputConnector.js b/frontend/src/Components/Form/IndexersSelectInputConnector.js
index fade1e758..e7cca1feb 100644
--- a/frontend/src/Components/Form/IndexersSelectInputConnector.js
+++ b/frontend/src/Components/Form/IndexersSelectInputConnector.js
@@ -1,20 +1,18 @@
-import { groupBy, map } from 'lodash';
+import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
-import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
-import sortByProp from 'Utilities/Array/sortByProp';
import titleCase from 'Utilities/String/titleCase';
import EnhancedSelectInput from './EnhancedSelectInput';
function createMapStateToProps() {
return createSelector(
(state, { value }) => value,
- createSortedSectionSelector('indexers', sortByProp('name')),
+ (state) => state.indexers,
(value, indexers) => {
const values = [];
- const groupedIndexers = map(groupBy(indexers.items, 'protocol'), (val, key) => ({ protocol: key, indexers: val }));
+ const groupedIndexers = _.map(_.groupBy(indexers.items, 'protocol'), (val, key) => ({ protocol: key, indexers: val }));
groupedIndexers.forEach((element) => {
values.push({
@@ -27,7 +25,6 @@ function createMapStateToProps() {
values.push({
key: indexer.id,
value: indexer.name,
- hint: `(${indexer.id})`,
isDisabled: !indexer.enable,
parentKey: element.protocol === 'usenet' ? -1 : -2
});
@@ -53,6 +50,7 @@ class IndexersSelectInputConnector extends Component {
// Render
render() {
+
return (
-
-
+
);
}
}
diff --git a/frontend/src/Components/Form/NumberInput.js b/frontend/src/Components/Form/NumberInput.js
index cac274d95..454aad997 100644
--- a/frontend/src/Components/Form/NumberInput.js
+++ b/frontend/src/Components/Form/NumberInput.js
@@ -41,7 +41,7 @@ class NumberInput extends Component {
componentDidUpdate(prevProps, prevState) {
const { value } = this.props;
- if (!isNaN(value) && value !== prevProps.value && !this.state.isFocused) {
+ if (value !== prevProps.value && !this.state.isFocused) {
this.setState({
value: value == null ? '' : value.toString()
});
diff --git a/frontend/src/Components/Form/PasswordInput.css b/frontend/src/Components/Form/PasswordInput.css
new file mode 100644
index 000000000..6cb162784
--- /dev/null
+++ b/frontend/src/Components/Form/PasswordInput.css
@@ -0,0 +1,5 @@
+.input {
+ composes: input from '~Components/Form/TextInput.css';
+
+ font-family: $passwordFamily;
+}
diff --git a/frontend/src/Components/Table/Cells/TableRowCellButton.css.d.ts b/frontend/src/Components/Form/PasswordInput.css.d.ts
similarity index 89%
rename from frontend/src/Components/Table/Cells/TableRowCellButton.css.d.ts
rename to frontend/src/Components/Form/PasswordInput.css.d.ts
index c748f6f97..774807ef4 100644
--- a/frontend/src/Components/Table/Cells/TableRowCellButton.css.d.ts
+++ b/frontend/src/Components/Form/PasswordInput.css.d.ts
@@ -1,7 +1,7 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
- 'cell': string;
+ 'input': string;
}
export const cssExports: CssExports;
export default cssExports;
diff --git a/frontend/src/Components/Form/PasswordInput.js b/frontend/src/Components/Form/PasswordInput.js
index dbc4cfdb4..fef54fd5a 100644
--- a/frontend/src/Components/Form/PasswordInput.js
+++ b/frontend/src/Components/Form/PasswordInput.js
@@ -1,5 +1,7 @@
+import PropTypes from 'prop-types';
import React from 'react';
import TextInput from './TextInput';
+import styles from './PasswordInput.css';
// Prevent a user from copying (or cutting) the password from the input
function onCopy(e) {
@@ -11,14 +13,17 @@ function PasswordInput(props) {
return (
);
}
PasswordInput.propTypes = {
- ...TextInput.props
+ className: PropTypes.string.isRequired
+};
+
+PasswordInput.defaultProps = {
+ className: styles.input
};
export default PasswordInput;
diff --git a/frontend/src/Components/Form/PathInputConnector.js b/frontend/src/Components/Form/PathInputConnector.js
index 563437f9a..3917a8d3f 100644
--- a/frontend/src/Components/Form/PathInputConnector.js
+++ b/frontend/src/Components/Form/PathInputConnector.js
@@ -68,7 +68,6 @@ class PathInputConnector extends Component {
}
PathInputConnector.propTypes = {
- ...PathInput.props,
includeFiles: PropTypes.bool.isRequired,
dispatchFetchPaths: PropTypes.func.isRequired,
dispatchClearPaths: PropTypes.func.isRequired
diff --git a/frontend/src/Components/Form/SelectInput.css b/frontend/src/Components/Form/SelectInput.css
index f6806b065..aa1dfc79b 100644
--- a/frontend/src/Components/Form/SelectInput.css
+++ b/frontend/src/Components/Form/SelectInput.css
@@ -1,14 +1,7 @@
.select {
- @add-mixin truncate;
-
composes: input from '~Components/Form/Input.css';
- padding: 0 30px 0 11px;
- background-image: none, linear-gradient(-135deg, transparent 50%, var(--inputBackgroundColor) 50%), linear-gradient(-225deg, transparent 50%, var(--inputBackgroundColor) 50%), linear-gradient(var(--inputBackgroundColor) 42%, var(--textColor) 42%);
- background-position: right 30px center, right bottom, right bottom, right bottom;
- background-size: 1px 100%, 35px 27px, 30px 35px, 30px 100%;
- background-repeat: no-repeat;
- appearance: none;
+ padding: 0 11px;
}
.hasError {
diff --git a/frontend/src/Components/Form/SelectInput.js b/frontend/src/Components/Form/SelectInput.js
index 553501afc..0a60ffe1e 100644
--- a/frontend/src/Components/Form/SelectInput.js
+++ b/frontend/src/Components/Form/SelectInput.js
@@ -61,7 +61,7 @@ class SelectInput extends Component {
value={key}
{...otherOptionProps}
>
- {typeof optionValue === 'function' ? optionValue() : optionValue}
+ {optionValue}
);
})
@@ -75,7 +75,7 @@ SelectInput.propTypes = {
className: PropTypes.string,
disabledClassName: PropTypes.string,
name: PropTypes.string.isRequired,
- value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.func]).isRequired,
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
isDisabled: PropTypes.bool,
hasError: PropTypes.bool,
diff --git a/frontend/src/Components/Icon.js b/frontend/src/Components/Icon.js
index d200b8c08..e8c7c5178 100644
--- a/frontend/src/Components/Icon.js
+++ b/frontend/src/Components/Icon.js
@@ -41,7 +41,7 @@ class Icon extends PureComponent {
return (
{icon}
@@ -58,7 +58,7 @@ Icon.propTypes = {
name: PropTypes.object.isRequired,
kind: PropTypes.string.isRequired,
size: PropTypes.number.isRequired,
- title: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
+ title: PropTypes.string,
isSpinning: PropTypes.bool.isRequired,
fixedWidth: PropTypes.bool.isRequired
};
diff --git a/frontend/src/Components/Label.js b/frontend/src/Components/Label.js
new file mode 100644
index 000000000..844da8165
--- /dev/null
+++ b/frontend/src/Components/Label.js
@@ -0,0 +1,48 @@
+import classNames from 'classnames';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { kinds, sizes } from 'Helpers/Props';
+import styles from './Label.css';
+
+function Label(props) {
+ const {
+ className,
+ kind,
+ size,
+ outline,
+ children,
+ ...otherProps
+ } = props;
+
+ return (
+
+ {children}
+
+ );
+}
+
+Label.propTypes = {
+ className: PropTypes.string.isRequired,
+ title: PropTypes.string,
+ kind: PropTypes.oneOf(kinds.all).isRequired,
+ size: PropTypes.oneOf(sizes.all).isRequired,
+ outline: PropTypes.bool.isRequired,
+ children: PropTypes.node.isRequired
+};
+
+Label.defaultProps = {
+ className: styles.label,
+ kind: kinds.DEFAULT,
+ size: sizes.SMALL,
+ outline: false
+};
+
+export default Label;
diff --git a/frontend/src/Components/Label.tsx b/frontend/src/Components/Label.tsx
deleted file mode 100644
index 9ab360f42..000000000
--- a/frontend/src/Components/Label.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import classNames from 'classnames';
-import React, { ComponentProps, ReactNode } from 'react';
-import { kinds, sizes } from 'Helpers/Props';
-import { Kind } from 'Helpers/Props/kinds';
-import { Size } from 'Helpers/Props/sizes';
-import styles from './Label.css';
-
-export interface LabelProps extends ComponentProps<'span'> {
- kind?: Extract;
- size?: Extract;
- outline?: boolean;
- children: ReactNode;
-}
-
-export default function Label({
- className = styles.label,
- kind = kinds.DEFAULT,
- size = sizes.SMALL,
- outline = false,
- ...otherProps
-}: LabelProps) {
- return (
-
- );
-}
diff --git a/frontend/src/Components/Link/Button.js b/frontend/src/Components/Link/Button.js
new file mode 100644
index 000000000..cbe4691d4
--- /dev/null
+++ b/frontend/src/Components/Link/Button.js
@@ -0,0 +1,54 @@
+import classNames from 'classnames';
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import { align, kinds, sizes } from 'Helpers/Props';
+import Link from './Link';
+import styles from './Button.css';
+
+class Button extends Component {
+
+ //
+ // Render
+
+ render() {
+ const {
+ className,
+ buttonGroupPosition,
+ kind,
+ size,
+ children,
+ ...otherProps
+ } = this.props;
+
+ return (
+
+ {children}
+
+ );
+ }
+
+}
+
+Button.propTypes = {
+ className: PropTypes.string.isRequired,
+ buttonGroupPosition: PropTypes.oneOf(align.all),
+ kind: PropTypes.oneOf(kinds.all),
+ size: PropTypes.oneOf(sizes.all),
+ children: PropTypes.node
+};
+
+Button.defaultProps = {
+ className: styles.button,
+ kind: kinds.DEFAULT,
+ size: sizes.MEDIUM
+};
+
+export default Button;
diff --git a/frontend/src/Components/Link/Button.tsx b/frontend/src/Components/Link/Button.tsx
deleted file mode 100644
index cf2293f59..000000000
--- a/frontend/src/Components/Link/Button.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import classNames from 'classnames';
-import React from 'react';
-import { align, kinds, sizes } from 'Helpers/Props';
-import { Kind } from 'Helpers/Props/kinds';
-import { Size } from 'Helpers/Props/sizes';
-import Link, { LinkProps } from './Link';
-import styles from './Button.css';
-
-export interface ButtonProps extends Omit {
- buttonGroupPosition?: Extract<
- (typeof align.all)[number],
- keyof typeof styles
- >;
- kind?: Extract;
- size?: Extract;
- children: Required;
-}
-
-export default function Button({
- className = styles.button,
- buttonGroupPosition,
- kind = kinds.DEFAULT,
- size = sizes.MEDIUM,
- ...otherProps
-}: ButtonProps) {
- return (
-
- );
-}
diff --git a/frontend/src/Components/Link/ClipboardButton.js b/frontend/src/Components/Link/ClipboardButton.js
new file mode 100644
index 000000000..55843f05f
--- /dev/null
+++ b/frontend/src/Components/Link/ClipboardButton.js
@@ -0,0 +1,139 @@
+import Clipboard from 'clipboard';
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import FormInputButton from 'Components/Form/FormInputButton';
+import Icon from 'Components/Icon';
+import { icons, kinds } from 'Helpers/Props';
+import getUniqueElememtId from 'Utilities/getUniqueElementId';
+import styles from './ClipboardButton.css';
+
+class ClipboardButton extends Component {
+
+ //
+ // Lifecycle
+
+ constructor(props, context) {
+ super(props, context);
+
+ this._id = getUniqueElememtId();
+ this._successTimeout = null;
+ this._testResultTimeout = null;
+
+ this.state = {
+ showSuccess: false,
+ showError: false
+ };
+ }
+
+ componentDidMount() {
+ this._clipboard = new Clipboard(`#${this._id}`, {
+ text: () => this.props.value,
+ container: document.getElementById(this._id)
+ });
+
+ this._clipboard.on('success', this.onSuccess);
+ }
+
+ componentDidUpdate() {
+ const {
+ showSuccess,
+ showError
+ } = this.state;
+
+ if (showSuccess || showError) {
+ this._testResultTimeout = setTimeout(this.resetState, 3000);
+ }
+ }
+
+ componentWillUnmount() {
+ if (this._clipboard) {
+ this._clipboard.destroy();
+ }
+
+ if (this._testResultTimeout) {
+ clearTimeout(this._testResultTimeout);
+ }
+ }
+
+ //
+ // Control
+
+ resetState = () => {
+ this.setState({
+ showSuccess: false,
+ showError: false
+ });
+ };
+
+ //
+ // Listeners
+
+ onSuccess = () => {
+ this.setState({
+ showSuccess: true
+ });
+ };
+
+ onError = () => {
+ this.setState({
+ showError: true
+ });
+ };
+
+ //
+ // Render
+
+ render() {
+ const {
+ value,
+ className,
+ ...otherProps
+ } = this.props;
+
+ const {
+ showSuccess,
+ showError
+ } = this.state;
+
+ const showStateIcon = showSuccess || showError;
+ const iconName = showError ? icons.DANGER : icons.CHECK;
+ const iconKind = showError ? kinds.DANGER : kinds.SUCCESS;
+
+ return (
+
+
+ {
+ showSuccess &&
+
+
+
+ }
+
+ {
+
+
+
+ }
+
+
+ );
+ }
+}
+
+ClipboardButton.propTypes = {
+ className: PropTypes.string.isRequired,
+ value: PropTypes.string.isRequired
+};
+
+ClipboardButton.defaultProps = {
+ className: styles.button
+};
+
+export default ClipboardButton;
diff --git a/frontend/src/Components/Link/ClipboardButton.tsx b/frontend/src/Components/Link/ClipboardButton.tsx
deleted file mode 100644
index dfce115ac..000000000
--- a/frontend/src/Components/Link/ClipboardButton.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import copy from 'copy-to-clipboard';
-import React, { useCallback, useEffect, useState } from 'react';
-import FormInputButton from 'Components/Form/FormInputButton';
-import Icon from 'Components/Icon';
-import { icons, kinds } from 'Helpers/Props';
-import { ButtonProps } from './Button';
-import styles from './ClipboardButton.css';
-
-export interface ClipboardButtonProps extends Omit {
- value: string;
-}
-
-export type ClipboardState = 'success' | 'error' | null;
-
-export default function ClipboardButton({
- id,
- value,
- className = styles.button,
- ...otherProps
-}: ClipboardButtonProps) {
- const [state, setState] = useState(null);
-
- useEffect(() => {
- if (!state) {
- return;
- }
-
- const timeoutId = setTimeout(() => {
- setState(null);
- }, 3000);
-
- return () => {
- if (timeoutId) {
- clearTimeout(timeoutId);
- }
- };
- }, [state]);
-
- const handleClick = useCallback(async () => {
- try {
- if ('clipboard' in navigator) {
- await navigator.clipboard.writeText(value);
- } else {
- copy(value);
- }
-
- setState('success');
- } catch (e) {
- setState('error');
- console.error(`Failed to copy to clipboard`, e);
- }
- }, [value]);
-
- return (
-
-
- {state ? (
-
-
-
- ) : null}
-
-
-
-
-
-
- );
-}
diff --git a/frontend/src/Components/Link/Link.js b/frontend/src/Components/Link/Link.js
new file mode 100644
index 000000000..6b5baca4e
--- /dev/null
+++ b/frontend/src/Components/Link/Link.js
@@ -0,0 +1,98 @@
+import classNames from 'classnames';
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import { Link as RouterLink } from 'react-router-dom';
+import styles from './Link.css';
+
+class Link extends Component {
+
+ //
+ // Listeners
+
+ onClick = (event) => {
+ const {
+ isDisabled,
+ onPress
+ } = this.props;
+
+ if (!isDisabled && onPress) {
+ onPress(event);
+ }
+ };
+
+ //
+ // Render
+
+ render() {
+ const {
+ className,
+ component,
+ to,
+ target,
+ isDisabled,
+ noRouter,
+ onPress,
+ ...otherProps
+ } = this.props;
+
+ const linkProps = { target };
+ let el = component;
+
+ if (to) {
+ if ((/\w+?:\/\//).test(to)) {
+ el = 'a';
+ linkProps.href = to;
+ linkProps.target = target || '_blank';
+ linkProps.rel = 'noreferrer';
+ } else if (noRouter) {
+ el = 'a';
+ linkProps.href = to;
+ linkProps.target = target || '_self';
+ } else {
+ el = RouterLink;
+ linkProps.to = `${window.Prowlarr.urlBase}/${to.replace(/^\//, '')}`;
+ linkProps.target = target;
+ }
+ }
+
+ if (el === 'button' || el === 'input') {
+ linkProps.type = otherProps.type || 'button';
+ linkProps.disabled = isDisabled;
+ }
+
+ linkProps.className = classNames(
+ className,
+ styles.link,
+ to && styles.to,
+ isDisabled && 'isDisabled'
+ );
+
+ const props = {
+ ...otherProps,
+ ...linkProps
+ };
+
+ props.onClick = this.onClick;
+
+ return (
+ React.createElement(el, props)
+ );
+ }
+}
+
+Link.propTypes = {
+ className: PropTypes.string,
+ component: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
+ to: PropTypes.string,
+ target: PropTypes.string,
+ isDisabled: PropTypes.bool,
+ noRouter: PropTypes.bool,
+ onPress: PropTypes.func
+};
+
+Link.defaultProps = {
+ component: 'button',
+ noRouter: false
+};
+
+export default Link;
diff --git a/frontend/src/Components/Link/Link.tsx b/frontend/src/Components/Link/Link.tsx
deleted file mode 100644
index 6f1fd1ff7..000000000
--- a/frontend/src/Components/Link/Link.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import classNames from 'classnames';
-import React, {
- ComponentPropsWithoutRef,
- ElementType,
- SyntheticEvent,
- useCallback,
-} from 'react';
-import { Link as RouterLink } from 'react-router-dom';
-import styles from './Link.css';
-
-export type LinkProps =
- ComponentPropsWithoutRef & {
- component?: C;
- to?: string;
- target?: string;
- isDisabled?: LinkProps['disabled'];
- noRouter?: boolean;
- onPress?(event: SyntheticEvent): void;
- };
-
-export default function Link({
- className,
- component,
- to,
- target,
- type,
- isDisabled,
- noRouter,
- onPress,
- ...otherProps
-}: LinkProps) {
- const Component = component || 'button';
-
- const onClick = useCallback(
- (event: SyntheticEvent) => {
- if (isDisabled) {
- return;
- }
-
- onPress?.(event);
- },
- [isDisabled, onPress]
- );
-
- const linkClass = classNames(
- className,
- styles.link,
- to && styles.to,
- isDisabled && 'isDisabled'
- );
-
- if (to) {
- const toLink = /\w+?:\/\//.test(to);
-
- if (toLink || noRouter) {
- return (
-
- );
- }
-
- return (
-
- );
- }
-
- return (
-
- );
-}
diff --git a/frontend/src/Components/Link/SpinnerErrorButton.js b/frontend/src/Components/Link/SpinnerErrorButton.js
index b0f39bc26..81d34f7c2 100644
--- a/frontend/src/Components/Link/SpinnerErrorButton.js
+++ b/frontend/src/Components/Link/SpinnerErrorButton.js
@@ -97,7 +97,6 @@ class SpinnerErrorButton extends Component {
render() {
const {
- kind,
isSpinning,
error,
children,
@@ -113,7 +112,7 @@ class SpinnerErrorButton extends Component {
const showIcon = wasSuccessful || hasWarning || hasError;
let iconName = icons.CHECK;
- let iconKind = kind === kinds.PRIMARY ? kinds.DEFAULT : kinds.SUCCESS;
+ let iconKind = kinds.SUCCESS;
if (hasWarning) {
iconName = icons.WARNING;
@@ -127,7 +126,6 @@ class SpinnerErrorButton extends Component {
return (
@@ -156,7 +154,6 @@ class SpinnerErrorButton extends Component {
}
SpinnerErrorButton.propTypes = {
- kind: PropTypes.oneOf(kinds.all),
isSpinning: PropTypes.bool.isRequired,
error: PropTypes.object,
children: PropTypes.node.isRequired
diff --git a/frontend/src/Components/Markdown/InlineMarkdown.js b/frontend/src/Components/Markdown/InlineMarkdown.js
index 993bb241e..dc9ea9bf3 100644
--- a/frontend/src/Components/Markdown/InlineMarkdown.js
+++ b/frontend/src/Components/Markdown/InlineMarkdown.js
@@ -10,55 +10,27 @@ class InlineMarkdown extends Component {
render() {
const {
className,
- data,
- blockClassName
+ data
} = this.props;
- // For now only replace links or code blocks (not both)
+ // For now only replace links
const markdownBlocks = [];
if (data) {
- const linkRegex = RegExp(/\[(.+?)\]\((.+?)\)/g);
+ const regex = RegExp(/\[(.+?)\]\((.+?)\)/g);
let endIndex = 0;
let match = null;
-
- while ((match = linkRegex.exec(data)) !== null) {
+ while ((match = regex.exec(data)) !== null) {
if (match.index > endIndex) {
markdownBlocks.push(data.substr(endIndex, match.index - endIndex));
}
-
markdownBlocks.push({match[1]});
endIndex = match.index + match[0].length;
}
- if (endIndex !== data.length && markdownBlocks.length > 0) {
+ if (endIndex !== data.length) {
markdownBlocks.push(data.substr(endIndex, data.length - endIndex));
}
-
- const codeRegex = RegExp(/(?=`)`(?!`)[^`]*(?=`)`(?!`)/g);
-
- endIndex = 0;
- match = null;
- let matchedCode = false;
-
- while ((match = codeRegex.exec(data)) !== null) {
- matchedCode = true;
-
- if (match.index > endIndex) {
- markdownBlocks.push(data.substr(endIndex, match.index - endIndex));
- }
-
- markdownBlocks.push({match[0].substring(1, match[0].length - 1)}
);
- endIndex = match.index + match[0].length;
- }
-
- if (endIndex !== data.length && markdownBlocks.length > 0 && matchedCode) {
- markdownBlocks.push(data.substr(endIndex, data.length - endIndex));
- }
-
- if (markdownBlocks.length === 0) {
- markdownBlocks.push(data);
- }
}
return {markdownBlocks};
@@ -67,8 +39,7 @@ class InlineMarkdown extends Component {
InlineMarkdown.propTypes = {
className: PropTypes.string,
- data: PropTypes.string,
- blockClassName: PropTypes.string
+ data: PropTypes.string
};
export default InlineMarkdown;
diff --git a/frontend/src/Components/Menu/FilterMenuContent.js b/frontend/src/Components/Menu/FilterMenuContent.js
index 7bc23c066..1fdb2476f 100644
--- a/frontend/src/Components/Menu/FilterMenuContent.js
+++ b/frontend/src/Components/Menu/FilterMenuContent.js
@@ -1,6 +1,5 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
-import sortByProp from 'Utilities/Array/sortByProp';
import translate from 'Utilities/String/translate';
import FilterMenuItem from './FilterMenuItem';
import MenuContent from './MenuContent';
@@ -34,33 +33,25 @@ class FilterMenuContent extends Component {
selectedFilterKey={selectedFilterKey}
onPress={onFilterSelect}
>
- {typeof filter.label === 'function' ? filter.label() : filter.label}
+ {filter.label}
);
})
}
{
- customFilters.length > 0 ?
- :
- null
- }
-
- {
- customFilters
- .sort(sortByProp('label'))
- .map((filter) => {
- return (
-
- {filter.label}
-
- );
- })
+ customFilters.map((filter) => {
+ return (
+
+ {filter.label}
+
+ );
+ })
}
{
diff --git a/frontend/src/Components/Modal/ModalContent.js b/frontend/src/Components/Modal/ModalContent.js
index 1d3862a13..8883bf2b9 100644
--- a/frontend/src/Components/Modal/ModalContent.js
+++ b/frontend/src/Components/Modal/ModalContent.js
@@ -3,7 +3,6 @@ import React from 'react';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import { icons } from 'Helpers/Props';
-import translate from 'Utilities/String/translate';
import styles from './ModalContent.css';
function ModalContent(props) {
@@ -29,7 +28,6 @@ function ModalContent(props) {
}
diff --git a/frontend/src/Components/Page/ErrorPage.js b/frontend/src/Components/Page/ErrorPage.js
index a2b0fca32..77bed6a31 100644
--- a/frontend/src/Components/Page/ErrorPage.js
+++ b/frontend/src/Components/Page/ErrorPage.js
@@ -7,7 +7,6 @@ function ErrorPage(props) {
const {
version,
isLocalStorageSupported,
- translationsError,
indexersError,
indexerStatusError,
indexerCategoriesError,
@@ -22,8 +21,6 @@ function ErrorPage(props) {
if (!isLocalStorageSupported) {
errorMessage = 'Local Storage is not supported or disabled. A plugin or private browsing may have disabled it.';
- } else if (translationsError) {
- errorMessage = getErrorMessage(translationsError, 'Failed to load translations from API');
} else if (indexersError) {
errorMessage = getErrorMessage(indexersError, 'Failed to load indexers from API');
} else if (indexerStatusError) {
@@ -58,7 +55,6 @@ function ErrorPage(props) {
ErrorPage.propTypes = {
version: PropTypes.string.isRequired,
isLocalStorageSupported: PropTypes.bool.isRequired,
- translationsError: PropTypes.object,
indexersError: PropTypes.object,
indexerStatusError: PropTypes.object,
indexerCategoriesError: PropTypes.object,
diff --git a/frontend/src/Components/Page/Header/PageHeader.js b/frontend/src/Components/Page/Header/PageHeader.js
index b032c1eb3..1d8b0cc55 100644
--- a/frontend/src/Components/Page/Header/PageHeader.js
+++ b/frontend/src/Components/Page/Header/PageHeader.js
@@ -7,7 +7,7 @@ import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import IndexerSearchInputConnector from './IndexerSearchInputConnector';
import KeyboardShortcutsModal from './KeyboardShortcutsModal';
-import PageHeaderActionsMenu from './PageHeaderActionsMenu';
+import PageHeaderActionsMenuConnector from './PageHeaderActionsMenuConnector';
import styles from './PageHeader.css';
class PageHeader extends Component {
@@ -78,7 +78,6 @@ class PageHeader extends Component {
aria-label="Donate"
to="https://prowlarr.com/donate"
size={14}
- title={translate('Donate')}
/>
-
-
diff --git a/frontend/src/Components/Page/Header/PageHeaderActionsMenu.js b/frontend/src/Components/Page/Header/PageHeaderActionsMenu.js
new file mode 100644
index 000000000..87fee6b0d
--- /dev/null
+++ b/frontend/src/Components/Page/Header/PageHeaderActionsMenu.js
@@ -0,0 +1,89 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import Icon from 'Components/Icon';
+import Menu from 'Components/Menu/Menu';
+import MenuButton from 'Components/Menu/MenuButton';
+import MenuContent from 'Components/Menu/MenuContent';
+import MenuItem from 'Components/Menu/MenuItem';
+import MenuItemSeparator from 'Components/Menu/MenuItemSeparator';
+import { align, icons, kinds } from 'Helpers/Props';
+import translate from 'Utilities/String/translate';
+import styles from './PageHeaderActionsMenu.css';
+
+function PageHeaderActionsMenu(props) {
+ const {
+ formsAuth,
+ onKeyboardShortcutsPress,
+ onRestartPress,
+ onShutdownPress
+ } = props;
+
+ return (
+
+
+
+ );
+}
+
+PageHeaderActionsMenu.propTypes = {
+ formsAuth: PropTypes.bool.isRequired,
+ onKeyboardShortcutsPress: PropTypes.func.isRequired,
+ onRestartPress: PropTypes.func.isRequired,
+ onShutdownPress: PropTypes.func.isRequired
+};
+
+export default PageHeaderActionsMenu;
diff --git a/frontend/src/Components/Page/Header/PageHeaderActionsMenu.tsx b/frontend/src/Components/Page/Header/PageHeaderActionsMenu.tsx
deleted file mode 100644
index 6b7da03eb..000000000
--- a/frontend/src/Components/Page/Header/PageHeaderActionsMenu.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import React, { useCallback } from 'react';
-import { useDispatch, useSelector } from 'react-redux';
-import AppState from 'App/State/AppState';
-import Icon from 'Components/Icon';
-import Menu from 'Components/Menu/Menu';
-import MenuButton from 'Components/Menu/MenuButton';
-import MenuContent from 'Components/Menu/MenuContent';
-import MenuItem from 'Components/Menu/MenuItem';
-import MenuItemSeparator from 'Components/Menu/MenuItemSeparator';
-import { align, icons, kinds } from 'Helpers/Props';
-import { restart, shutdown } from 'Store/Actions/systemActions';
-import translate from 'Utilities/String/translate';
-import styles from './PageHeaderActionsMenu.css';
-
-interface PageHeaderActionsMenuProps {
- onKeyboardShortcutsPress(): void;
-}
-
-function PageHeaderActionsMenu(props: PageHeaderActionsMenuProps) {
- const { onKeyboardShortcutsPress } = props;
-
- const dispatch = useDispatch();
-
- const { authentication, isDocker } = useSelector(
- (state: AppState) => state.system.status.item
- );
-
- const formsAuth = authentication === 'forms';
-
- const handleRestartPress = useCallback(() => {
- dispatch(restart());
- }, [dispatch]);
-
- const handleShutdownPress = useCallback(() => {
- dispatch(shutdown());
- }, [dispatch]);
-
- return (
-
-
-
- );
-}
-
-export default PageHeaderActionsMenu;
diff --git a/frontend/src/Components/Page/Header/PageHeaderActionsMenuConnector.js b/frontend/src/Components/Page/Header/PageHeaderActionsMenuConnector.js
new file mode 100644
index 000000000..3aba95065
--- /dev/null
+++ b/frontend/src/Components/Page/Header/PageHeaderActionsMenuConnector.js
@@ -0,0 +1,56 @@
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import { createSelector } from 'reselect';
+import { restart, shutdown } from 'Store/Actions/systemActions';
+import PageHeaderActionsMenu from './PageHeaderActionsMenu';
+
+function createMapStateToProps() {
+ return createSelector(
+ (state) => state.system.status,
+ (status) => {
+ return {
+ formsAuth: status.item.authentication === 'forms'
+ };
+ }
+ );
+}
+
+const mapDispatchToProps = {
+ restart,
+ shutdown
+};
+
+class PageHeaderActionsMenuConnector extends Component {
+
+ //
+ // Listeners
+
+ onRestartPress = () => {
+ this.props.restart();
+ };
+
+ onShutdownPress = () => {
+ this.props.shutdown();
+ };
+
+ //
+ // Render
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+PageHeaderActionsMenuConnector.propTypes = {
+ restart: PropTypes.func.isRequired,
+ shutdown: PropTypes.func.isRequired
+};
+
+export default connect(createMapStateToProps, mapDispatchToProps)(PageHeaderActionsMenuConnector);
diff --git a/frontend/src/Components/Page/Page.js b/frontend/src/Components/Page/Page.js
index c2e368827..aa23f4d88 100644
--- a/frontend/src/Components/Page/Page.js
+++ b/frontend/src/Components/Page/Page.js
@@ -1,8 +1,8 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
-import AppUpdatedModal from 'App/AppUpdatedModal';
+import AppUpdatedModalConnector from 'App/AppUpdatedModalConnector';
import ColorImpairedContext from 'App/ColorImpairedContext';
-import ConnectionLostModal from 'App/ConnectionLostModal';
+import ConnectionLostModalConnector from 'App/ConnectionLostModalConnector';
import SignalRConnector from 'Components/SignalRConnector';
import AuthenticationRequiredModal from 'FirstRun/AuthenticationRequiredModal';
import locationShape from 'Helpers/Props/Shapes/locationShape';
@@ -102,12 +102,12 @@ class Page extends Component {
{children}
-
-
diff --git a/frontend/src/Components/Page/PageConnector.js b/frontend/src/Components/Page/PageConnector.js
index 5c1f6f42e..5ac032c0f 100644
--- a/frontend/src/Components/Page/PageConnector.js
+++ b/frontend/src/Components/Page/PageConnector.js
@@ -3,7 +3,7 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { createSelector } from 'reselect';
-import { fetchTranslations, saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions';
+import { saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions';
import { fetchCustomFilters } from 'Store/Actions/customFilterActions';
import { fetchIndexers } from 'Store/Actions/indexerActions';
import { fetchIndexerStatus } from 'Store/Actions/indexerStatusActions';
@@ -54,7 +54,6 @@ const selectIsPopulated = createSelector(
(state) => state.indexerStatus.isPopulated,
(state) => state.settings.indexerCategories.isPopulated,
(state) => state.system.status.isPopulated,
- (state) => state.app.translations.isPopulated,
(
customFiltersIsPopulated,
tagsIsPopulated,
@@ -64,8 +63,7 @@ const selectIsPopulated = createSelector(
indexersIsPopulated,
indexerStatusIsPopulated,
indexerCategoriesIsPopulated,
- systemStatusIsPopulated,
- translationsIsPopulated
+ systemStatusIsPopulated
) => {
return (
customFiltersIsPopulated &&
@@ -76,8 +74,7 @@ const selectIsPopulated = createSelector(
indexersIsPopulated &&
indexerStatusIsPopulated &&
indexerCategoriesIsPopulated &&
- systemStatusIsPopulated &&
- translationsIsPopulated
+ systemStatusIsPopulated
);
}
);
@@ -92,7 +89,6 @@ const selectErrors = createSelector(
(state) => state.indexerStatus.error,
(state) => state.settings.indexerCategories.error,
(state) => state.system.status.error,
- (state) => state.app.translations.error,
(
customFiltersError,
tagsError,
@@ -102,8 +98,7 @@ const selectErrors = createSelector(
indexersError,
indexerStatusError,
indexerCategoriesError,
- systemStatusError,
- translationsError
+ systemStatusError
) => {
const hasError = !!(
customFiltersError ||
@@ -114,8 +109,7 @@ const selectErrors = createSelector(
indexersError ||
indexerStatusError ||
indexerCategoriesError ||
- systemStatusError ||
- translationsError
+ systemStatusError
);
return {
@@ -128,8 +122,7 @@ const selectErrors = createSelector(
indexersError,
indexerStatusError,
indexerCategoriesError,
- systemStatusError,
- translationsError
+ systemStatusError
};
}
);
@@ -191,9 +184,6 @@ function createMapDispatchToProps(dispatch, props) {
dispatchFetchStatus() {
dispatch(fetchStatus());
},
- dispatchFetchTranslations() {
- dispatch(fetchTranslations());
- },
onResize(dimensions) {
dispatch(saveDimensions(dimensions));
},
@@ -227,7 +217,6 @@ class PageConnector extends Component {
this.props.dispatchFetchUISettings();
this.props.dispatchFetchGeneralSettings();
this.props.dispatchFetchStatus();
- this.props.dispatchFetchTranslations();
}
}
@@ -253,7 +242,6 @@ class PageConnector extends Component {
dispatchFetchUISettings,
dispatchFetchGeneralSettings,
dispatchFetchStatus,
- dispatchFetchTranslations,
...otherProps
} = this.props;
@@ -294,7 +282,6 @@ PageConnector.propTypes = {
dispatchFetchUISettings: PropTypes.func.isRequired,
dispatchFetchGeneralSettings: PropTypes.func.isRequired,
dispatchFetchStatus: PropTypes.func.isRequired,
- dispatchFetchTranslations: PropTypes.func.isRequired,
onSidebarVisibleChange: PropTypes.func.isRequired
};
diff --git a/frontend/src/Components/Page/PageContentBody.tsx b/frontend/src/Components/Page/PageContentBody.tsx
index ce9b0e7e4..75317f113 100644
--- a/frontend/src/Components/Page/PageContentBody.tsx
+++ b/frontend/src/Components/Page/PageContentBody.tsx
@@ -1,19 +1,22 @@
-import React, { ForwardedRef, forwardRef, ReactNode, useCallback } from 'react';
-import Scroller, { OnScroll } from 'Components/Scroller/Scroller';
+import React, { forwardRef, ReactNode, useCallback } from 'react';
+import Scroller from 'Components/Scroller/Scroller';
import ScrollDirection from 'Helpers/Props/ScrollDirection';
import { isLocked } from 'Utilities/scrollLock';
import styles from './PageContentBody.css';
interface PageContentBodyProps {
- className?: string;
- innerClassName?: string;
+ className: string;
+ innerClassName: string;
children: ReactNode;
initialScrollTop?: number;
- onScroll?: (payload: OnScroll) => void;
+ onScroll?: (payload) => void;
}
const PageContentBody = forwardRef(
- (props: PageContentBodyProps, ref: ForwardedRef
) => {
+ (
+ props: PageContentBodyProps,
+ ref: React.MutableRefObject
+ ) => {
const {
className = styles.contentBody,
innerClassName = styles.innerContentBody,
@@ -23,7 +26,7 @@ const PageContentBody = forwardRef(
} = props;
const onScrollWrapper = useCallback(
- (payload: OnScroll) => {
+ (payload) => {
if (onScroll && !isLocked()) {
onScroll(payload);
}
diff --git a/frontend/src/Components/Page/PageJumpBar.css b/frontend/src/Components/Page/PageJumpBar.css
index f5ae7a729..9a116fb54 100644
--- a/frontend/src/Components/Page/PageJumpBar.css
+++ b/frontend/src/Components/Page/PageJumpBar.css
@@ -1,5 +1,4 @@
.jumpBar {
- z-index: $pageJumpBarZIndex;
display: flex;
align-content: stretch;
align-items: stretch;
diff --git a/frontend/src/Components/Page/Sidebar/PageSidebar.js b/frontend/src/Components/Page/Sidebar/PageSidebar.js
index 6eef54eab..045789075 100644
--- a/frontend/src/Components/Page/Sidebar/PageSidebar.js
+++ b/frontend/src/Components/Page/Sidebar/PageSidebar.js
@@ -8,7 +8,7 @@ import Scroller from 'Components/Scroller/Scroller';
import { icons } from 'Helpers/Props';
import locationShape from 'Helpers/Props/Shapes/locationShape';
import dimensions from 'Styles/Variables/dimensions';
-import HealthStatus from 'System/Status/Health/HealthStatus';
+import HealthStatusConnector from 'System/Status/Health/HealthStatusConnector';
import translate from 'Utilities/String/translate';
import MessagesConnector from './Messages/MessagesConnector';
import PageSidebarItem from './PageSidebarItem';
@@ -20,12 +20,12 @@ const SIDEBAR_WIDTH = parseInt(dimensions.sidebarWidth);
const links = [
{
iconName: icons.MOVIE_CONTINUING,
- title: () => translate('Indexers'),
+ title: translate('Indexers'),
to: '/',
alias: '/indexers',
children: [
{
- title: () => translate('Stats'),
+ title: translate('Stats'),
to: '/indexers/stats'
}
]
@@ -33,47 +33,47 @@ const links = [
{
iconName: icons.SEARCH,
- title: () => translate('Search'),
+ title: translate('Search'),
to: '/search'
},
{
iconName: icons.ACTIVITY,
- title: () => translate('History'),
+ title: translate('History'),
to: '/history'
},
{
iconName: icons.SETTINGS,
- title: () => translate('Settings'),
+ title: translate('Settings'),
to: '/settings',
children: [
{
- title: () => translate('Indexers'),
+ title: translate('Indexers'),
to: '/settings/indexers'
},
{
- title: () => translate('Apps'),
+ title: translate('Apps'),
to: '/settings/applications'
},
{
- title: () => translate('DownloadClients'),
+ title: translate('DownloadClients'),
to: '/settings/downloadclients'
},
{
- title: () => translate('Connect'),
+ title: translate('Connect'),
to: '/settings/connect'
},
{
- title: () => translate('Tags'),
+ title: translate('Tags'),
to: '/settings/tags'
},
{
- title: () => translate('General'),
+ title: translate('General'),
to: '/settings/general'
},
{
- title: () => translate('UI'),
+ title: translate('UI'),
to: '/settings/ui'
}
]
@@ -81,32 +81,32 @@ const links = [
{
iconName: icons.SYSTEM,
- title: () => translate('System'),
+ title: translate('System'),
to: '/system/status',
children: [
{
- title: () => translate('Status'),
+ title: translate('Status'),
to: '/system/status',
- statusComponent: HealthStatus
+ statusComponent: HealthStatusConnector
},
{
- title: () => translate('Tasks'),
+ title: translate('Tasks'),
to: '/system/tasks'
},
{
- title: () => translate('Backup'),
+ title: translate('Backup'),
to: '/system/backup'
},
{
- title: () => translate('Updates'),
+ title: translate('Updates'),
to: '/system/updates'
},
{
- title: () => translate('Events'),
+ title: translate('Events'),
to: '/system/events'
},
{
- title: () => translate('LogFiles'),
+ title: translate('LogFiles'),
to: '/system/logs/files'
}
]
diff --git a/frontend/src/Components/Page/Sidebar/PageSidebarItem.css b/frontend/src/Components/Page/Sidebar/PageSidebarItem.css
index 409062f97..5e3e3b52c 100644
--- a/frontend/src/Components/Page/Sidebar/PageSidebarItem.css
+++ b/frontend/src/Components/Page/Sidebar/PageSidebarItem.css
@@ -24,7 +24,6 @@
composes: link;
padding: 10px 24px;
- padding-left: 35px;
}
.isActiveLink {
@@ -42,6 +41,10 @@
text-align: center;
}
+.noIcon {
+ margin-left: 25px;
+}
+
.status {
float: right;
}
diff --git a/frontend/src/Components/Page/Sidebar/PageSidebarItem.css.d.ts b/frontend/src/Components/Page/Sidebar/PageSidebarItem.css.d.ts
index 5bf0eb815..77e23c767 100644
--- a/frontend/src/Components/Page/Sidebar/PageSidebarItem.css.d.ts
+++ b/frontend/src/Components/Page/Sidebar/PageSidebarItem.css.d.ts
@@ -8,6 +8,7 @@ interface CssExports {
'isActiveParentLink': string;
'item': string;
'link': string;
+ 'noIcon': string;
'status': string;
}
export const cssExports: CssExports;
diff --git a/frontend/src/Components/Page/Sidebar/PageSidebarItem.js b/frontend/src/Components/Page/Sidebar/PageSidebarItem.js
index 8d0e4e790..9ad78db6b 100644
--- a/frontend/src/Components/Page/Sidebar/PageSidebarItem.js
+++ b/frontend/src/Components/Page/Sidebar/PageSidebarItem.js
@@ -63,7 +63,9 @@ class PageSidebarItem extends Component {
}
- {typeof title === 'function' ? title() : title}
+
+ {title}
+
{
!!StatusComponent &&
@@ -86,7 +88,7 @@ class PageSidebarItem extends Component {
PageSidebarItem.propTypes = {
iconName: PropTypes.object,
- title: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
+ title: PropTypes.string.isRequired,
to: PropTypes.string.isRequired,
isActive: PropTypes.bool,
isActiveParent: PropTypes.bool,
diff --git a/frontend/src/Components/Page/Toolbar/PageToolbarButton.css b/frontend/src/Components/Page/Toolbar/PageToolbarButton.css
index e9a1b666d..0b6918296 100644
--- a/frontend/src/Components/Page/Toolbar/PageToolbarButton.css
+++ b/frontend/src/Components/Page/Toolbar/PageToolbarButton.css
@@ -22,14 +22,11 @@
display: flex;
align-items: center;
justify-content: center;
- overflow: hidden;
height: 24px;
}
.label {
padding: 0 3px;
- max-width: 100%;
- max-height: 100%;
color: var(--toolbarLabelColor);
font-size: $extraSmallFontSize;
line-height: calc($extraSmallFontSize + 1px);
diff --git a/frontend/src/Components/Page/Toolbar/PageToolbarButton.js b/frontend/src/Components/Page/Toolbar/PageToolbarButton.js
index 675bdfd02..c93603aa9 100644
--- a/frontend/src/Components/Page/Toolbar/PageToolbarButton.js
+++ b/frontend/src/Components/Page/Toolbar/PageToolbarButton.js
@@ -23,7 +23,6 @@ function PageToolbarButton(props) {
isDisabled && styles.isDisabled
)}
isDisabled={isDisabled || isSpinning}
- title={label}
{...otherProps}
>
void;
+ onScroll?: (payload) => void;
}
const Scroller = forwardRef(
- (props: ScrollerProps, ref: ForwardedRef) => {
+ (props: ScrollerProps, ref: React.MutableRefObject) => {
const {
className,
autoFocus = false,
@@ -42,7 +30,7 @@ const Scroller = forwardRef(
} = props;
const internalRef = useRef();
- const currentRef = (ref as MutableRefObject) ?? internalRef;
+ const currentRef = ref ?? internalRef;
useEffect(
() => {
diff --git a/frontend/src/Components/SignalRConnector.js b/frontend/src/Components/SignalRConnector.js
index d39c05e10..28c12df12 100644
--- a/frontend/src/Components/SignalRConnector.js
+++ b/frontend/src/Components/SignalRConnector.js
@@ -141,16 +141,6 @@ class SignalRConnector extends Component {
console.error(`signalR: Unable to find handler for ${name}`);
};
- handleApplications = ({ action, resource }) => {
- const section = 'settings.applications';
-
- if (action === 'created' || action === 'updated') {
- this.props.dispatchUpdateItem({ section, ...resource });
- } else if (action === 'deleted') {
- this.props.dispatchRemoveItem({ section, id: resource.id });
- }
- };
-
handleCommand = (body) => {
if (body.action === 'sync') {
this.props.dispatchFetchCommands();
@@ -160,8 +150,8 @@ class SignalRConnector extends Component {
const resource = body.resource;
const status = resource.status;
- // Both successful and failed commands need to be
- // completed, otherwise they spin until they time out.
+ // Both sucessful and failed commands need to be
+ // completed, otherwise they spin until they timeout.
if (status === 'completed' || status === 'failed') {
this.props.dispatchFinishCommand(resource);
@@ -170,16 +160,6 @@ class SignalRConnector extends Component {
}
};
- handleDownloadclient = ({ action, resource }) => {
- const section = 'settings.downloadClients';
-
- if (action === 'created' || action === 'updated') {
- this.props.dispatchUpdateItem({ section, ...resource });
- } else if (action === 'deleted') {
- this.props.dispatchRemoveItem({ section, id: resource.id });
- }
- };
-
handleHealth = () => {
this.props.dispatchFetchHealth();
};
@@ -188,33 +168,14 @@ class SignalRConnector extends Component {
this.props.dispatchFetchIndexerStatus();
};
- handleIndexer = ({ action, resource }) => {
+ handleIndexer = (body) => {
+ const action = body.action;
const section = 'indexers';
- if (action === 'created' || action === 'updated') {
- this.props.dispatchUpdateItem({ section, ...resource });
+ if (action === 'updated') {
+ this.props.dispatchUpdateItem({ section, ...body.resource });
} else if (action === 'deleted') {
- this.props.dispatchRemoveItem({ section, id: resource.id });
- }
- };
-
- handleIndexerproxy = ({ action, resource }) => {
- const section = 'settings.indexerProxies';
-
- if (action === 'created' || action === 'updated') {
- this.props.dispatchUpdateItem({ section, ...resource });
- } else if (action === 'deleted') {
- this.props.dispatchRemoveItem({ section, id: resource.id });
- }
- };
-
- handleNotification = ({ action, resource }) => {
- const section = 'settings.notifications';
-
- if (action === 'created' || action === 'updated') {
- this.props.dispatchUpdateItem({ section, ...resource });
- } else if (action === 'deleted') {
- this.props.dispatchRemoveItem({ section, id: resource.id });
+ this.props.dispatchRemoveItem({ section, id: body.resource.id });
}
};
diff --git a/frontend/src/Components/Table/Cells/RelativeDateCell.js b/frontend/src/Components/Table/Cells/RelativeDateCell.js
index 4bf94cf57..207b97752 100644
--- a/frontend/src/Components/Table/Cells/RelativeDateCell.js
+++ b/frontend/src/Components/Table/Cells/RelativeDateCell.js
@@ -1,66 +1,58 @@
import PropTypes from 'prop-types';
-import React from 'react';
-import { useSelector } from 'react-redux';
-import { createSelector } from 'reselect';
-import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
+import React, { PureComponent } from 'react';
import formatDateTime from 'Utilities/Date/formatDateTime';
import getRelativeDate from 'Utilities/Date/getRelativeDate';
import TableRowCell from './TableRowCell';
import styles from './RelativeDateCell.css';
-function createRelativeDateCellSelector() {
- return createSelector(createUISettingsSelector(), (uiSettings) => {
- return {
- showRelativeDates: uiSettings.showRelativeDates,
- shortDateFormat: uiSettings.shortDateFormat,
- longDateFormat: uiSettings.longDateFormat,
- timeFormat: uiSettings.timeFormat
- };
- });
-}
+class RelativeDateCell extends PureComponent {
-function RelativeDateCell(props) {
//
// Render
- const {
- className,
- date,
- includeSeconds,
- component: Component,
- dispatch,
- ...otherProps
- } = props;
+ render() {
+ const {
+ className,
+ date,
+ includeSeconds,
+ showRelativeDates,
+ shortDateFormat,
+ longDateFormat,
+ timeFormat,
+ component: Component,
+ dispatch,
+ ...otherProps
+ } = this.props;
- const { showRelativeDates, shortDateFormat, longDateFormat, timeFormat } =
- useSelector(createRelativeDateCellSelector());
+ if (!date) {
+ return (
+
+ );
+ }
- if (!date) {
- return ;
+ return (
+
+ {getRelativeDate(date, shortDateFormat, showRelativeDates, { timeFormat, includeSeconds, timeForToday: true })}
+
+ );
}
-
- return (
-
- {getRelativeDate(date, shortDateFormat, showRelativeDates, {
- timeFormat,
- includeSeconds,
- timeForToday: true
- })}
-
- );
}
RelativeDateCell.propTypes = {
className: PropTypes.string.isRequired,
date: PropTypes.string,
includeSeconds: PropTypes.bool.isRequired,
+ showRelativeDates: PropTypes.bool.isRequired,
+ shortDateFormat: PropTypes.string.isRequired,
+ longDateFormat: PropTypes.string.isRequired,
+ timeFormat: PropTypes.string.isRequired,
component: PropTypes.elementType,
dispatch: PropTypes.func
};
diff --git a/frontend/src/Components/Table/Cells/TableRowCellButton.js b/frontend/src/Components/Table/Cells/TableRowCellButton.js
new file mode 100644
index 000000000..ff50d3bc9
--- /dev/null
+++ b/frontend/src/Components/Table/Cells/TableRowCellButton.js
@@ -0,0 +1,25 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import Link from 'Components/Link/Link';
+import TableRowCell from './TableRowCell';
+import styles from './TableRowCellButton.css';
+
+function TableRowCellButton({ className, ...otherProps }) {
+ return (
+
+ );
+}
+
+TableRowCellButton.propTypes = {
+ className: PropTypes.string.isRequired
+};
+
+TableRowCellButton.defaultProps = {
+ className: styles.cell
+};
+
+export default TableRowCellButton;
diff --git a/frontend/src/Components/Table/Cells/TableRowCellButton.tsx b/frontend/src/Components/Table/Cells/TableRowCellButton.tsx
deleted file mode 100644
index c80a3d626..000000000
--- a/frontend/src/Components/Table/Cells/TableRowCellButton.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import React, { ReactNode } from 'react';
-import Link, { LinkProps } from 'Components/Link/Link';
-import TableRowCell from './TableRowCell';
-import styles from './TableRowCellButton.css';
-
-interface TableRowCellButtonProps extends LinkProps {
- className?: string;
- children: ReactNode;
-}
-
-function TableRowCellButton(props: TableRowCellButtonProps) {
- const { className = styles.cell, ...otherProps } = props;
-
- return (
-
- );
-}
-
-export default TableRowCellButton;
diff --git a/frontend/src/Components/Table/Column.ts b/frontend/src/Components/Table/Column.ts
index 24674c3fc..8c2122c65 100644
--- a/frontend/src/Components/Table/Column.ts
+++ b/frontend/src/Components/Table/Column.ts
@@ -1,12 +1,8 @@
import React from 'react';
-type PropertyFunction = () => T;
-
-// TODO: Convert to generic so `name` can be a type
interface Column {
name: string;
- label: string | PropertyFunction | React.ReactNode;
- className?: string;
+ label: string | React.ReactNode;
columnLabel?: string;
isSortable?: boolean;
isVisible: boolean;
diff --git a/frontend/src/Components/Table/Table.js b/frontend/src/Components/Table/Table.js
index 8afbf9ea0..befc8219a 100644
--- a/frontend/src/Components/Table/Table.js
+++ b/frontend/src/Components/Table/Table.js
@@ -107,7 +107,7 @@ function Table(props) {
{...getTableHeaderCellProps(otherProps)}
{...column}
>
- {typeof column.label === 'function' ? column.label() : column.label}
+ {column.label}
);
})
diff --git a/frontend/src/Components/Table/TableHeaderCell.js b/frontend/src/Components/Table/TableHeaderCell.js
index b0ed5c571..21766978b 100644
--- a/frontend/src/Components/Table/TableHeaderCell.js
+++ b/frontend/src/Components/Table/TableHeaderCell.js
@@ -30,7 +30,6 @@ class TableHeaderCell extends Component {
const {
className,
name,
- label,
columnLabel,
isSortable,
isVisible,
@@ -54,8 +53,7 @@ class TableHeaderCell extends Component {
{...otherProps}
component="th"
className={className}
- label={typeof label === 'function' ? label() : label}
- title={typeof columnLabel === 'function' ? columnLabel() : columnLabel}
+ title={columnLabel}
onPress={this.onPress}
>
{children}
@@ -79,8 +77,7 @@ class TableHeaderCell extends Component {
TableHeaderCell.propTypes = {
className: PropTypes.string,
name: PropTypes.string.isRequired,
- label: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.node]),
- columnLabel: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
+ columnLabel: PropTypes.string,
isSortable: PropTypes.bool,
isVisible: PropTypes.bool,
isModifiable: PropTypes.bool,
diff --git a/frontend/src/Components/Table/TableOptions/TableOptionsColumn.js b/frontend/src/Components/Table/TableOptions/TableOptionsColumn.js
index 402ef5ae1..2d91c7c63 100644
--- a/frontend/src/Components/Table/TableOptions/TableOptionsColumn.js
+++ b/frontend/src/Components/Table/TableOptions/TableOptionsColumn.js
@@ -35,7 +35,7 @@ function TableOptionsColumn(props) {
isDisabled={isModifiable === false}
onChange={onVisibleChange}
/>
- {typeof label === 'function' ? label() : label}
+ {label}
{
@@ -56,7 +56,7 @@ function TableOptionsColumn(props) {
TableOptionsColumn.propTypes = {
name: PropTypes.string.isRequired,
- label: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
+ label: PropTypes.string.isRequired,
isVisible: PropTypes.bool.isRequired,
isModifiable: PropTypes.bool.isRequired,
index: PropTypes.number.isRequired,
diff --git a/frontend/src/Components/Table/TableOptions/TableOptionsColumnDragSource.js b/frontend/src/Components/Table/TableOptions/TableOptionsColumnDragSource.js
index 77d18463f..100559660 100644
--- a/frontend/src/Components/Table/TableOptions/TableOptionsColumnDragSource.js
+++ b/frontend/src/Components/Table/TableOptions/TableOptionsColumnDragSource.js
@@ -112,7 +112,7 @@ class TableOptionsColumnDragSource extends Component {
void;
-}
-
-function usePaging(options: PagingOptions) {
- const { page, totalPages, gotoPage } = options;
- const dispatch = useDispatch();
-
- const handleFirstPagePress = useCallback(() => {
- dispatch(gotoPage({ page: 1 }));
- }, [dispatch, gotoPage]);
-
- const handlePreviousPagePress = useCallback(() => {
- dispatch(gotoPage({ page: Math.max(page - 1, 1) }));
- }, [page, dispatch, gotoPage]);
-
- const handleNextPagePress = useCallback(() => {
- dispatch(gotoPage({ page: Math.min(page + 1, totalPages) }));
- }, [page, totalPages, dispatch, gotoPage]);
-
- const handleLastPagePress = useCallback(() => {
- dispatch(gotoPage({ page: totalPages }));
- }, [totalPages, dispatch, gotoPage]);
-
- const handlePageSelect = useCallback(
- (page: number) => {
- dispatch(gotoPage({ page }));
- },
- [dispatch, gotoPage]
- );
-
- return useMemo(() => {
- return {
- handleFirstPagePress,
- handlePreviousPagePress,
- handleNextPagePress,
- handleLastPagePress,
- handlePageSelect,
- };
- }, [
- handleFirstPagePress,
- handlePreviousPagePress,
- handleNextPagePress,
- handleLastPagePress,
- handlePageSelect,
- ]);
-}
-
-export default usePaging;
diff --git a/frontend/src/Components/TagList.js b/frontend/src/Components/TagList.js
index fe700b8fe..f4d4e2af4 100644
--- a/frontend/src/Components/TagList.js
+++ b/frontend/src/Components/TagList.js
@@ -1,15 +1,14 @@
import PropTypes from 'prop-types';
import React from 'react';
import { kinds } from 'Helpers/Props';
-import sortByProp from 'Utilities/Array/sortByProp';
import Label from './Label';
import styles from './TagList.css';
function TagList({ tags, tagList }) {
const sortedTags = tags
.map((tagId) => tagList.find((tag) => tag.id === tagId))
- .filter((tag) => !!tag)
- .sort(sortByProp('label'));
+ .filter((t) => t !== undefined)
+ .sort((a, b) => a.label.localeCompare(b.label));
return (
diff --git a/frontend/src/Components/keyboardShortcuts.js b/frontend/src/Components/keyboardShortcuts.js
index 8513a65eb..b576a988c 100644
--- a/frontend/src/Components/keyboardShortcuts.js
+++ b/frontend/src/Components/keyboardShortcuts.js
@@ -6,51 +6,37 @@ import translate from 'Utilities/String/translate';
export const shortcuts = {
OPEN_KEYBOARD_SHORTCUTS_MODAL: {
key: '?',
- get name() {
- return translate('OpenThisModal');
- }
+ name: translate('OpenThisModal')
},
CLOSE_MODAL: {
key: 'Esc',
- get name() {
- return translate('CloseCurrentModal');
- }
+ name: translate('CloseCurrentModal')
},
ACCEPT_CONFIRM_MODAL: {
key: 'Enter',
- get name() {
- return translate('AcceptConfirmationModal');
- }
+ name: translate('AcceptConfirmationModal')
},
MOVIE_SEARCH_INPUT: {
key: 's',
- get name() {
- return translate('FocusSearchBox');
- }
+ name: translate('FocusSearchBox')
},
SAVE_SETTINGS: {
key: 'mod+s',
- get name() {
- return translate('SaveSettings');
- }
+ name: translate('SaveSettings')
},
SCROLL_TOP: {
key: 'mod+home',
- get name() {
- return translate('MovieIndexScrollTop');
- }
+ name: translate('MovieIndexScrollTop')
},
SCROLL_BOTTOM: {
key: 'mod+end',
- get name() {
- return translate('MovieIndexScrollBottom');
- }
+ name: translate('MovieIndexScrollBottom')
}
};
diff --git a/frontend/src/Components/withScrollPosition.tsx b/frontend/src/Components/withScrollPosition.tsx
index f688a6253..ec13c6ab8 100644
--- a/frontend/src/Components/withScrollPosition.tsx
+++ b/frontend/src/Components/withScrollPosition.tsx
@@ -1,30 +1,24 @@
+import PropTypes from 'prop-types';
import React from 'react';
-import { RouteComponentProps } from 'react-router-dom';
import scrollPositions from 'Store/scrollPositions';
-interface WrappedComponentProps {
- initialScrollTop: number;
-}
-
-interface ScrollPositionProps {
- history: RouteComponentProps['history'];
- location: RouteComponentProps['location'];
- match: RouteComponentProps['match'];
-}
-
-function withScrollPosition(
- WrappedComponent: React.FC
,
- scrollPositionKey: string
-) {
- function ScrollPosition(props: ScrollPositionProps) {
+function withScrollPosition(WrappedComponent, scrollPositionKey) {
+ function ScrollPosition(props) {
const { history } = props;
const initialScrollTop =
- history.action === 'POP' ? scrollPositions[scrollPositionKey] : 0;
+ history.action === 'POP' ||
+ (history.location.state && history.location.state.restoreScrollPosition)
+ ? scrollPositions[scrollPositionKey]
+ : 0;
return ;
}
+ ScrollPosition.propTypes = {
+ history: PropTypes.object.isRequired,
+ };
+
return ScrollPosition;
}
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/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/FirstRun/AuthenticationRequiredModalContent.js b/frontend/src/FirstRun/AuthenticationRequiredModalContent.js
index 17a04e403..920c59a31 100644
--- a/frontend/src/FirstRun/AuthenticationRequiredModalContent.js
+++ b/frontend/src/FirstRun/AuthenticationRequiredModalContent.js
@@ -11,7 +11,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
-import { authenticationMethodOptions, authenticationRequiredOptions } from 'Settings/General/SecuritySettings';
+import { authenticationMethodOptions, authenticationRequiredOptions, authenticationRequiredWarning } from 'Settings/General/SecuritySettings';
import translate from 'Utilities/String/translate';
import styles from './AuthenticationRequiredModalContent.css';
@@ -34,8 +34,7 @@ function AuthenticationRequiredModalContent(props) {
authenticationMethod,
authenticationRequired,
username,
- password,
- passwordConfirmation
+ password
} = settings;
const authenticationEnabled = authenticationMethod && authenticationMethod.value !== 'none';
@@ -64,75 +63,71 @@ function AuthenticationRequiredModalContent(props) {
className={styles.authRequiredAlert}
kind={kinds.WARNING}
>
- {translate('AuthenticationRequiredWarning')}
+ {authenticationRequiredWarning}
{
isPopulated && !error ?
- {translate('AuthenticationMethod')}
+ {translate('Authentication')}
-
- {translate('AuthenticationRequired')}
+ {
+ authenticationEnabled ?
+
+ {translate('AuthenticationRequired')}
-
-
+
+ :
+ null
+ }
-
- {translate('Username')}
+ {
+ authenticationEnabled ?
+
+ {translate('Username')}
-
-
+
+ :
+ null
+ }
-
- {translate('Password')}
+ {
+ authenticationEnabled ?
+
+ {translate('Password')}
-
-
-
-
- {translate('PasswordConfirmation')}
-
-
-
+
+ :
+ null
+ }
:
null
}
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..7fed535f2 100644
--- a/frontend/src/Helpers/Props/filterBuilderValueTypes.js
+++ b/frontend/src/Helpers/Props/filterBuilderValueTypes.js
@@ -2,10 +2,9 @@ export const BOOL = 'bool';
export const BYTES = 'bytes';
export const DATE = 'date';
export const DEFAULT = 'default';
-export const HISTORY_EVENT_TYPE = 'historyEventType';
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..589add5a8 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,
@@ -76,7 +75,6 @@ import {
faListCheck as fasListCheck,
faLocationArrow as fasLocationArrow,
faLock as fasLock,
- faMagnet as fasMagnet,
faMedkit as fasMedkit,
faMinus as fasMinus,
faMusic as fasMusic,
@@ -142,7 +140,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;
@@ -184,7 +181,6 @@ export const INTERACTIVE = fasUser;
export const KEYBOARD = farKeyboard;
export const LOCK = fasLock;
export const LOGOUT = fasSignOutAlt;
-export const MAGNET = fasMagnet;
export const MANAGE = fasListCheck;
export const MEDIA_INFO = farFileInvoice;
export const MISSING = fasExclamationTriangle;
diff --git a/frontend/src/Helpers/Props/inputTypes.js b/frontend/src/Helpers/Props/inputTypes.js
index f9cd58e6d..d26d08616 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';
@@ -9,7 +10,6 @@ export const INFO = 'info';
export const MOVIE_MONITORED_SELECT = 'movieMonitoredSelect';
export const CATEGORY_SELECT = 'newznabCategorySelect';
export const DOWNLOAD_CLIENT_SELECT = 'downloadClientSelect';
-export const FLOAT = 'float';
export const NUMBER = 'number';
export const OAUTH = 'oauth';
export const PASSWORD = 'password';
@@ -26,6 +26,7 @@ export const TAG_SELECT = 'tagSelect';
export const all = [
AUTO_COMPLETE,
APP_PROFILE_SELECT,
+ AVAILABILITY_SELECT,
CAPTCHA,
CARDIGANNCAPTCHA,
CHECK,
@@ -34,7 +35,6 @@ export const all = [
INFO,
MOVIE_MONITORED_SELECT,
CATEGORY_SELECT,
- FLOAT,
NUMBER,
OAUTH,
PASSWORD,
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.css b/frontend/src/History/Details/HistoryDetailsModal.css
new file mode 100644
index 000000000..271d422ff
--- /dev/null
+++ b/frontend/src/History/Details/HistoryDetailsModal.css
@@ -0,0 +1,5 @@
+.markAsFailedButton {
+ composes: button from '~Components/Link/Button.css';
+
+ margin-right: auto;
+}
diff --git a/frontend/src/Components/Form/InfoInput.css.d.ts b/frontend/src/History/Details/HistoryDetailsModal.css.d.ts
similarity index 83%
rename from frontend/src/Components/Form/InfoInput.css.d.ts
rename to frontend/src/History/Details/HistoryDetailsModal.css.d.ts
index 65c237dff..a8cc499e2 100644
--- a/frontend/src/Components/Form/InfoInput.css.d.ts
+++ b/frontend/src/History/Details/HistoryDetailsModal.css.d.ts
@@ -1,7 +1,7 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
- 'message': string;
+ 'markAsFailedButton': string;
}
export const cssExports: CssExports;
export default cssExports;
diff --git a/frontend/src/History/Details/HistoryDetailsModal.js b/frontend/src/History/Details/HistoryDetailsModal.js
index 560955de3..e6f960c48 100644
--- a/frontend/src/History/Details/HistoryDetailsModal.js
+++ b/frontend/src/History/Details/HistoryDetailsModal.js
@@ -1,13 +1,16 @@
import PropTypes from 'prop-types';
import React from 'react';
import Button from 'Components/Link/Button';
+import SpinnerButton from 'Components/Link/SpinnerButton';
import Modal from 'Components/Modal/Modal';
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 { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import HistoryDetails from './HistoryDetails';
+import styles from './HistoryDetailsModal.css';
function getHeaderTitle(eventType) {
switch (eventType) {
@@ -29,10 +32,11 @@ function HistoryDetailsModal(props) {
isOpen,
eventType,
indexer,
- date,
data,
+ isMarkingAsFailed,
shortDateFormat,
timeFormat,
+ onMarkAsFailedPress,
onModalClose
} = props;
@@ -50,7 +54,6 @@ function HistoryDetailsModal(props) {
+ {
+ eventType === 'grabbed' &&
+
+ Mark as Failed
+
+ }
+
+ );
+ }
+}
+
+HistoryRowParameter.propTypes = {
+ title: PropTypes.string.isRequired,
+ value: PropTypes.string.isRequired
+};
+
+export default HistoryRowParameter;
diff --git a/frontend/src/History/HistoryRowParameter.tsx b/frontend/src/History/HistoryRowParameter.tsx
deleted file mode 100644
index ad83d5d77..000000000
--- a/frontend/src/History/HistoryRowParameter.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import React from 'react';
-import Link from 'Components/Link/Link';
-import { HistoryQueryType } from 'typings/History';
-import styles from './HistoryRowParameter.css';
-
-interface HistoryRowParameterProps {
- title: string;
- value: string;
- queryType: HistoryQueryType;
-}
-
-function HistoryRowParameter(props: HistoryRowParameterProps) {
- const { title, value, queryType } = props;
-
- const type = title.toLowerCase();
-
- let link = null;
-
- if (type === 'imdb') {
- link = {value};
- } else if (type === 'tmdb') {
- link = (
-
- {value}
-
- );
- } else if (type === 'tvdb') {
- link = (
-
- {value}
-
- );
- } else if (type === 'tvmaze') {
- link = {value};
- }
-
- return (
-
-
- {title}
-
-
-
{link ? link : value}
-
- );
-}
-
-export default HistoryRowParameter;
diff --git a/frontend/src/Indexer/Add/AddIndexerModal.js b/frontend/src/Indexer/Add/AddIndexerModal.js
new file mode 100644
index 000000000..4c4db24b9
--- /dev/null
+++ b/frontend/src/Indexer/Add/AddIndexerModal.js
@@ -0,0 +1,29 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import Modal from 'Components/Modal/Modal';
+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..5a92b40cb 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 {
@@ -46,6 +40,7 @@
flex: 1;
flex-direction: column;
margin-right: 12px;
+ max-width: 50%;
}
.filterContainer:last-child {
@@ -58,22 +53,17 @@
}
@media only screen and (max-width: $breakpointSmall) {
- .filterInput {
- margin-bottom: 5px;
- }
-
.alert {
display: none;
}
.filterRow {
- display: block;
- margin-bottom: 10px;
+ flex-direction: column;
}
.filterContainer {
margin-right: 0;
- margin-bottom: 5px;
+ margin-bottom: 12px;
}
.scroller {
@@ -83,12 +73,6 @@
}
}
-@media only screen and (min-width: $breakpointSmall) {
- .filterContainer {
- max-width: 50%;
- }
-}
-
.modalFooter {
composes: modalFooter from '~Components/Modal/ModalFooter.css';
@@ -104,8 +88,4 @@
flex-direction: column;
gap: 10px;
}
-
- .available {
- display: none;
- }
}
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..4617664ad
--- /dev/null
+++ b/frontend/src/Indexer/Add/AddIndexerModalContent.js
@@ -0,0 +1,311 @@
+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 SelectIndexerRowConnector from './SelectIndexerRowConnector';
+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
+ }
+];
+
+const protocols = [
+ {
+ key: 'torrent',
+ value: 'torrent'
+ },
+ {
+ key: 'usenet',
+ value: 'nzb'
+ }
+];
+
+const privacyLevels = [
+ {
+ key: 'private',
+ value: translate('Private')
+ },
+ {
+ key: 'semiPrivate',
+ value: translate('SemiPrivate')
+ },
+ {
+ key: 'public',
+ value: 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', [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..0dc810608
--- /dev/null
+++ b/frontend/src/Indexer/Add/AddIndexerModalContentConnector.js
@@ -0,0 +1,83 @@
+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 createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
+import AddIndexerModalContent from './AddIndexerModalContent';
+
+function createMapStateToProps() {
+ return createSelector(
+ createClientSideCollectionSelector('indexers.schema'),
+ (indexers) => {
+ const {
+ isFetching,
+ isPopulated,
+ error,
+ items,
+ sortDirection,
+ sortKey
+ } = indexers;
+
+ return {
+ isFetching,
+ isPopulated,
+ error,
+ indexers: items,
+ sortKey,
+ sortDirection
+ };
+ }
+ );
+}
+
+const mapDispatchToProps = {
+ fetchIndexerSchema,
+ selectIndexerSchema,
+ setIndexerSchemaSort
+};
+
+class AddIndexerModalContentConnector extends Component {
+
+ //
+ // Lifecycle
+
+ componentDidMount() {
+ this.props.fetchIndexerSchema();
+ }
+
+ //
+ // Listeners
+
+ onIndexerSelect = ({ implementation, name }) => {
+ this.props.selectIndexerSchema({ implementation, 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/AddIndexerPresetMenuItem.js b/frontend/src/Indexer/Add/AddIndexerPresetMenuItem.js
index 8f98d0e12..03196e526 100644
--- a/frontend/src/Indexer/Add/AddIndexerPresetMenuItem.js
+++ b/frontend/src/Indexer/Add/AddIndexerPresetMenuItem.js
@@ -10,14 +10,12 @@ class AddIndexerPresetMenuItem extends Component {
onPress = () => {
const {
name,
- implementation,
- implementationName
+ implementation
} = this.props;
this.props.onPress({
name,
- implementation,
- implementationName
+ implementation
});
};
@@ -28,7 +26,6 @@ class AddIndexerPresetMenuItem extends Component {
const {
name,
implementation,
- implementationName,
...otherProps
} = this.props;
@@ -46,7 +43,6 @@ class AddIndexerPresetMenuItem extends Component {
AddIndexerPresetMenuItem.propTypes = {
name: PropTypes.string.isRequired,
implementation: PropTypes.string.isRequired,
- implementationName: PropTypes.string.isRequired,
onPress: PropTypes.func.isRequired
};
diff --git a/frontend/src/Indexer/Add/SelectIndexerRow.js b/frontend/src/Indexer/Add/SelectIndexerRow.js
new file mode 100644
index 000000000..c3f33220d
--- /dev/null
+++ b/frontend/src/Indexer/Add/SelectIndexerRow.js
@@ -0,0 +1,88 @@
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import Icon from 'Components/Icon';
+import TableRowCell from 'Components/Table/Cells/TableRowCell';
+import TableRowButton from 'Components/Table/TableRowButton';
+import { icons } from 'Helpers/Props';
+import ProtocolLabel from 'Indexer/Index/Table/ProtocolLabel';
+import firstCharToUpper from 'Utilities/String/firstCharToUpper';
+import translate from 'Utilities/String/translate';
+import styles from './SelectIndexerRow.css';
+
+class SelectIndexerRow extends Component {
+
+ //
+ // Listeners
+
+ onPress = () => {
+ const {
+ implementation,
+ name
+ } = this.props;
+
+ this.props.onIndexerSelect({ implementation, name });
+ };
+
+ //
+ // Render
+
+ render() {
+ const {
+ protocol,
+ privacy,
+ name,
+ language,
+ description,
+ isExistingIndexer
+ } = this.props;
+
+ return (
+
+
+
+
+
+
+ {name}
+ {
+ isExistingIndexer ?
+ :
+ null
+ }
+
+
+
+ {language}
+
+
+
+ {description}
+
+
+
+ {translate(firstCharToUpper(privacy))}
+
+
+ );
+ }
+}
+
+SelectIndexerRow.propTypes = {
+ name: PropTypes.string.isRequired,
+ protocol: PropTypes.string.isRequired,
+ privacy: PropTypes.string.isRequired,
+ language: PropTypes.string.isRequired,
+ description: PropTypes.string.isRequired,
+ implementation: PropTypes.string.isRequired,
+ onIndexerSelect: PropTypes.func.isRequired,
+ isExistingIndexer: PropTypes.bool.isRequired
+};
+
+export default SelectIndexerRow;
diff --git a/frontend/src/Indexer/Add/SelectIndexerRow.tsx b/frontend/src/Indexer/Add/SelectIndexerRow.tsx
deleted file mode 100644
index 157050e41..000000000
--- a/frontend/src/Indexer/Add/SelectIndexerRow.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-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 translate from 'Utilities/String/translate';
-import styles from './SelectIndexerRow.css';
-
-interface SelectIndexerRowProps {
- name: string;
- protocol: DownloadProtocol;
- privacy: IndexerPrivacy;
- language: string;
- description: string;
- capabilities: IndexerCapabilities;
- implementation: string;
- implementationName: string;
- isExistingIndexer: boolean;
- onIndexerSelect(...args: unknown[]): void;
-}
-
-function SelectIndexerRow(props: SelectIndexerRowProps) {
- const {
- name,
- protocol,
- privacy,
- language,
- description,
- capabilities,
- implementation,
- implementationName,
- isExistingIndexer,
- onIndexerSelect,
- } = props;
-
- const onPress = useCallback(() => {
- onIndexerSelect({ implementation, implementationName, name });
- }, [implementation, implementationName, name, onIndexerSelect]);
-
- return (
-
-
-
-
-
-
- {name}
- {isExistingIndexer ? (
-
- ) : null}
-
-
- {language}
-
- {description}
-
-
-
-
-
-
-
-
-
- );
-}
-
-export default SelectIndexerRow;
diff --git a/frontend/src/Indexer/Add/SelectIndexerRowConnector.js b/frontend/src/Indexer/Add/SelectIndexerRowConnector.js
new file mode 100644
index 000000000..f507689c8
--- /dev/null
+++ b/frontend/src/Indexer/Add/SelectIndexerRowConnector.js
@@ -0,0 +1,18 @@
+
+import { connect } from 'react-redux';
+import { createSelector } from 'reselect';
+import createExistingIndexerSelector from 'Store/Selectors/createExistingIndexerSelector';
+import SelectIndexerRow from './SelectIndexerRow';
+
+function createMapStateToProps() {
+ return createSelector(
+ createExistingIndexerSelector(),
+ (isExistingIndexer, dimensions) => {
+ return {
+ isExistingIndexer
+ };
+ }
+ );
+}
+
+export default connect(createMapStateToProps)(SelectIndexerRow);
diff --git a/frontend/src/Indexer/Delete/DeleteIndexerModal.js b/frontend/src/Indexer/Delete/DeleteIndexerModal.js
new file mode 100644
index 000000000..aed954829
--- /dev/null
+++ b/frontend/src/Indexer/Delete/DeleteIndexerModal.js
@@ -0,0 +1,34 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import Modal from 'Components/Modal/Modal';
+import { sizes } from 'Helpers/Props';
+import DeleteIndexerModalContentConnector from './DeleteIndexerModalContentConnector';
+
+function DeleteIndexerModal(props) {
+ const {
+ isOpen,
+ onModalClose,
+ ...otherProps
+ } = props;
+
+ return (
+
+
+
+ );
+}
+
+DeleteIndexerModal.propTypes = {
+ ...DeleteIndexerModalContentConnector.propTypes,
+ isOpen: PropTypes.bool.isRequired,
+ onModalClose: PropTypes.func.isRequired
+};
+
+export default DeleteIndexerModal;
diff --git a/frontend/src/Indexer/Delete/DeleteIndexerModal.tsx b/frontend/src/Indexer/Delete/DeleteIndexerModal.tsx
deleted file mode 100644
index 13850aa77..000000000
--- a/frontend/src/Indexer/Delete/DeleteIndexerModal.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import React from 'react';
-import Modal from 'Components/Modal/Modal';
-import { sizes } from 'Helpers/Props';
-import DeleteIndexerModalContent from './DeleteIndexerModalContent';
-
-interface DeleteIndexerModalProps {
- isOpen: boolean;
- indexerId: number;
- onModalClose(): void;
-}
-
-function DeleteIndexerModal(props: DeleteIndexerModalProps) {
- const { isOpen, indexerId, onModalClose } = props;
-
- return (
-
-
-
- );
-}
-
-export default DeleteIndexerModal;
diff --git a/frontend/src/Indexer/Delete/DeleteIndexerModalContent.js b/frontend/src/Indexer/Delete/DeleteIndexerModalContent.js
new file mode 100644
index 000000000..e3d46e108
--- /dev/null
+++ b/frontend/src/Indexer/Delete/DeleteIndexerModalContent.js
@@ -0,0 +1,88 @@
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import Button from 'Components/Link/Button';
+import ModalBody from 'Components/Modal/ModalBody';
+import ModalContent from 'Components/Modal/ModalContent';
+import ModalFooter from 'Components/Modal/ModalFooter';
+import ModalHeader from 'Components/Modal/ModalHeader';
+import { kinds } from 'Helpers/Props';
+import translate from 'Utilities/String/translate';
+
+class DeleteIndexerModalContent extends Component {
+
+ //
+ // Lifecycle
+
+ constructor(props, context) {
+ super(props, context);
+
+ this.state = {
+ deleteFiles: false,
+ addImportExclusion: false
+ };
+ }
+
+ //
+ // Listeners
+
+ onDeleteFilesChange = ({ value }) => {
+ this.setState({ deleteFiles: value });
+ };
+
+ onAddImportExclusionChange = ({ value }) => {
+ this.setState({ addImportExclusion: value });
+ };
+
+ onDeleteMovieConfirmed = () => {
+ const deleteFiles = this.state.deleteFiles;
+ const addImportExclusion = this.state.addImportExclusion;
+
+ this.setState({ deleteFiles: false, addImportExclusion: false });
+ this.props.onDeletePress(deleteFiles, addImportExclusion);
+ };
+
+ //
+ // Render
+
+ render() {
+ const {
+ name,
+ onModalClose
+ } = this.props;
+
+ return (
+
+
+ Delete - {name}
+
+
+
+ {`Are you sure you want to delete ${name} from Prowlarr`}
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+DeleteIndexerModalContent.propTypes = {
+ name: PropTypes.string.isRequired,
+ onDeletePress: PropTypes.func.isRequired,
+ onModalClose: PropTypes.func.isRequired
+};
+
+export default DeleteIndexerModalContent;
diff --git a/frontend/src/Indexer/Delete/DeleteIndexerModalContent.tsx b/frontend/src/Indexer/Delete/DeleteIndexerModalContent.tsx
deleted file mode 100644
index aeae273a9..000000000
--- a/frontend/src/Indexer/Delete/DeleteIndexerModalContent.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import React, { useCallback } from 'react';
-import { useDispatch, useSelector } from 'react-redux';
-import Button from 'Components/Link/Button';
-import ModalBody from 'Components/Modal/ModalBody';
-import ModalContent from 'Components/Modal/ModalContent';
-import ModalFooter from 'Components/Modal/ModalFooter';
-import ModalHeader from 'Components/Modal/ModalHeader';
-import { kinds } from 'Helpers/Props';
-import Indexer from 'Indexer/Indexer';
-import { deleteIndexer } from 'Store/Actions/indexerActions';
-import { createIndexerSelectorForHook } from 'Store/Selectors/createIndexerSelector';
-import translate from 'Utilities/String/translate';
-
-interface DeleteIndexerModalContentProps {
- indexerId: number;
- onModalClose(): void;
-}
-
-function DeleteIndexerModalContent(props: DeleteIndexerModalContentProps) {
- const { indexerId, onModalClose } = props;
-
- const { name } = useSelector(
- createIndexerSelectorForHook(indexerId)
- ) as Indexer;
- const dispatch = useDispatch();
-
- const onConfirmDelete = useCallback(() => {
- dispatch(deleteIndexer({ id: indexerId }));
-
- onModalClose();
- }, [indexerId, dispatch, onModalClose]);
-
- return (
-
-
- {translate('Delete')} - {name}
-
-
-
- {translate('AreYouSureYouWantToDeleteIndexer', { name })}
-
-
-
-
-
-
-
-
- );
-}
-
-export default DeleteIndexerModalContent;
diff --git a/frontend/src/Indexer/Delete/DeleteIndexerModalContentConnector.js b/frontend/src/Indexer/Delete/DeleteIndexerModalContentConnector.js
new file mode 100644
index 000000000..1e92eb845
--- /dev/null
+++ b/frontend/src/Indexer/Delete/DeleteIndexerModalContentConnector.js
@@ -0,0 +1,57 @@
+import { push } from 'connected-react-router';
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import { createSelector } from 'reselect';
+import { deleteIndexer } from 'Store/Actions/indexerActions';
+import createIndexerSelector from 'Store/Selectors/createIndexerSelector';
+import DeleteIndexerModalContent from './DeleteIndexerModalContent';
+
+function createMapStateToProps() {
+ return createSelector(
+ createIndexerSelector(),
+ (indexer) => {
+ return indexer;
+ }
+ );
+}
+
+const mapDispatchToProps = {
+ deleteIndexer,
+ push
+};
+
+class DeleteIndexerModalContentConnector extends Component {
+
+ //
+ // Listeners
+
+ onDeletePress = () => {
+ this.props.deleteIndexer({
+ id: this.props.indexerId
+ });
+
+ this.props.onModalClose(true);
+ };
+
+ //
+ // Render
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+DeleteIndexerModalContentConnector.propTypes = {
+ indexerId: PropTypes.number.isRequired,
+ onModalClose: PropTypes.func.isRequired,
+ deleteIndexer: PropTypes.func.isRequired,
+ push: PropTypes.func.isRequired
+};
+
+export default connect(createMapStateToProps, mapDispatchToProps)(DeleteIndexerModalContentConnector);
diff --git a/frontend/src/Indexer/Edit/EditIndexerModalContent.js b/frontend/src/Indexer/Edit/EditIndexerModalContent.js
index 7dabc50d9..4a2ec4c0e 100644
--- a/frontend/src/Indexer/Edit/EditIndexerModalContent.js
+++ b/frontend/src/Indexer/Edit/EditIndexerModalContent.js
@@ -61,7 +61,7 @@ function EditIndexerModalContent(props) {
return (
- {id ? translate('EditIndexerImplementation', { implementationName: indexerDisplayName }) : translate('AddIndexerImplementation', { implementationName: indexerDisplayName })}
+ {`${id ? translate('EditIndexer') : translate('AddIndexer')} - ${indexerDisplayName}`}
@@ -97,7 +97,7 @@ function EditIndexerModalContent(props) {
@@ -144,7 +144,6 @@ function EditIndexerModalContent(props) {
}) :
null
}
-
diff --git a/frontend/src/Indexer/Index/IndexerIndex.tsx b/frontend/src/Indexer/Index/IndexerIndex.tsx
index e20e269f8..dcb7b8c9e 100644
--- a/frontend/src/Indexer/Index/IndexerIndex.tsx
+++ b/frontend/src/Indexer/Index/IndexerIndex.tsx
@@ -1,16 +1,6 @@
-import React, {
- useCallback,
- useEffect,
- useMemo,
- useRef,
- useState,
-} from 'react';
+import React, { useCallback, useMemo, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { SelectProvider } from 'App/SelectContext';
-import ClientSideCollectionAppState from 'App/State/ClientSideCollectionAppState';
-import IndexerAppState, {
- IndexerIndexAppState,
-} from 'App/State/IndexerAppState';
import { APP_INDEXER_SYNC } from 'Commands/commandNames';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
@@ -28,17 +18,12 @@ import AddIndexerModal from 'Indexer/Add/AddIndexerModal';
import EditIndexerModalConnector from 'Indexer/Edit/EditIndexerModalConnector';
import NoIndexer from 'Indexer/NoIndexer';
import { executeCommand } from 'Store/Actions/commandActions';
-import {
- cloneIndexer,
- fetchIndexers,
- testAllIndexers,
-} from 'Store/Actions/indexerActions';
+import { testAllIndexers } from 'Store/Actions/indexerActions';
import {
setIndexerFilter,
setIndexerSort,
setIndexerTableOption,
} from 'Store/Actions/indexerIndexActions';
-import { fetchIndexerStatus } from 'Store/Actions/indexerStatusActions';
import scrollPositions from 'Store/scrollPositions';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
@@ -56,7 +41,9 @@ import IndexerIndexTable from './Table/IndexerIndexTable';
import IndexerIndexTableOptions from './Table/IndexerIndexTableOptions';
import styles from './IndexerIndex.css';
-const getViewComponent = () => IndexerIndexTable;
+function getViewComponent() {
+ return IndexerIndexTable;
+}
interface IndexerIndexProps {
initialScrollTop?: number;
@@ -77,25 +64,27 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
sortKey,
sortDirection,
view,
- }: IndexerAppState & IndexerIndexAppState & ClientSideCollectionAppState =
- useSelector(createIndexerClientSideCollectionItemsSelector('indexerIndex'));
+ } = useSelector(
+ createIndexerClientSideCollectionItemsSelector('indexerIndex')
+ );
const isSyncingIndexers = useSelector(
createCommandExecutingSelector(APP_INDEXER_SYNC)
);
const { isSmallScreen } = useSelector(createDimensionsSelector());
const dispatch = useDispatch();
- const scrollerRef = useRef(null);
+ const scrollerRef = useRef();
const [isAddIndexerModalOpen, setIsAddIndexerModalOpen] = useState(false);
const [isEditIndexerModalOpen, setIsEditIndexerModalOpen] = useState(false);
- const [jumpToCharacter, setJumpToCharacter] = useState(
- undefined
- );
+ const [jumpToCharacter, setJumpToCharacter] = useState(null);
const [isSelectMode, setIsSelectMode] = useState(false);
- useEffect(() => {
- dispatch(fetchIndexers());
- dispatch(fetchIndexerStatus());
+ const onAppIndexerSyncPress = useCallback(() => {
+ dispatch(
+ executeCommand({
+ name: APP_INDEXER_SYNC,
+ })
+ );
}, [dispatch]);
const onAddIndexerPress = useCallback(() => {
@@ -114,24 +103,6 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
setIsEditIndexerModalOpen(false);
}, [setIsEditIndexerModalOpen]);
- const onCloneIndexerPress = useCallback(
- (id: number) => {
- dispatch(cloneIndexer({ id }));
-
- setIsEditIndexerModalOpen(true);
- },
- [dispatch, setIsEditIndexerModalOpen]
- );
-
- const onAppIndexerSyncPress = useCallback(() => {
- dispatch(
- executeCommand({
- name: APP_INDEXER_SYNC,
- forceSync: true,
- })
- );
- }, [dispatch]);
-
const onTestAllPress = useCallback(() => {
dispatch(testAllIndexers());
}, [dispatch]);
@@ -141,37 +112,37 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
}, [isSelectMode, setIsSelectMode]);
const onTableOptionChange = useCallback(
- (payload: unknown) => {
+ (payload) => {
dispatch(setIndexerTableOption(payload));
},
[dispatch]
);
const onSortSelect = useCallback(
- (value: string) => {
+ (value) => {
dispatch(setIndexerSort({ sortKey: value }));
},
[dispatch]
);
const onFilterSelect = useCallback(
- (value: string) => {
+ (value) => {
dispatch(setIndexerFilter({ selectedFilterKey: value }));
},
[dispatch]
);
const onJumpBarItemPress = useCallback(
- (character: string) => {
+ (character) => {
setJumpToCharacter(character);
},
[setJumpToCharacter]
);
const onScroll = useCallback(
- ({ scrollTop }: { scrollTop: number }) => {
- setJumpToCharacter(undefined);
- scrollPositions.indexerIndex = scrollTop;
+ ({ scrollTop }) => {
+ setJumpToCharacter(null);
+ scrollPositions.seriesIndex = scrollTop;
},
[setJumpToCharacter]
);
@@ -184,7 +155,7 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
};
}
- const characters = items.reduce((acc: Record, item) => {
+ const characters = items.reduce((acc, item) => {
let char = item.sortName.charAt(0);
if (!isNaN(Number(char))) {
@@ -306,8 +277,6 @@ const IndexerIndex = withScrollPosition((props: IndexerIndexProps) => {
{
jumpToCharacter={jumpToCharacter}
isSelectMode={isSelectMode}
isSmallScreen={isSmallScreen}
- onCloneIndexerPress={onCloneIndexerPress}
/>
diff --git a/frontend/src/Indexer/Index/IndexerIndexFilterModal.tsx b/frontend/src/Indexer/Index/IndexerIndexFilterModal.tsx
index 1b4bfb6de..8a151907a 100644
--- a/frontend/src/Indexer/Index/IndexerIndexFilterModal.tsx
+++ b/frontend/src/Indexer/Index/IndexerIndexFilterModal.tsx
@@ -1,13 +1,12 @@
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 { setIndexerFilter } from 'Store/Actions/indexerIndexActions';
function createIndexerSelector() {
return createSelector(
- (state: AppState) => state.indexers.items,
+ (state) => state.indexers.items,
(indexers) => {
return indexers;
}
@@ -16,20 +15,14 @@ function createIndexerSelector() {
function createFilterBuilderPropsSelector() {
return createSelector(
- (state: AppState) => state.indexerIndex.filterBuilderProps,
+ (state) => state.indexerIndex.filterBuilderProps,
(filterBuilderProps) => {
return filterBuilderProps;
}
);
}
-interface IndexerIndexFilterModalProps {
- isOpen: boolean;
-}
-
-export default function IndexerIndexFilterModal(
- props: IndexerIndexFilterModalProps
-) {
+export default function IndexerIndexFilterModal(props) {
const sectionItems = useSelector(createIndexerSelector());
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
const customFilterType = 'indexerIndex';
@@ -37,7 +30,7 @@ export default function IndexerIndexFilterModal(
const dispatch = useDispatch();
const dispatchSetFilter = useCallback(
- (payload: unknown) => {
+ (payload) => {
dispatch(setIndexerFilter(payload));
},
[dispatch]
@@ -45,7 +38,6 @@ export default function IndexerIndexFilterModal(
return (
{
+ (indexers) => {
return indexers.items.map((s) => {
const { protocol, privacy, enable } = s;
diff --git a/frontend/src/Indexer/Index/Menus/IndexerIndexFilterMenu.tsx b/frontend/src/Indexer/Index/Menus/IndexerIndexFilterMenu.tsx
index 57ebf7b2f..0b6021bad 100644
--- a/frontend/src/Indexer/Index/Menus/IndexerIndexFilterMenu.tsx
+++ b/frontend/src/Indexer/Index/Menus/IndexerIndexFilterMenu.tsx
@@ -1,18 +1,10 @@
+import PropTypes from 'prop-types';
import React from 'react';
-import { CustomFilter } from 'App/State/AppState';
import FilterMenu from 'Components/Menu/FilterMenu';
import { align } from 'Helpers/Props';
import IndexerIndexFilterModal from 'Indexer/Index/IndexerIndexFilterModal';
-interface IndexerIndexFilterMenuProps {
- selectedFilterKey: string | number;
- filters: object[];
- customFilters: CustomFilter[];
- isDisabled: boolean;
- onFilterSelect(filterName: string): unknown;
-}
-
-function IndexerIndexFilterMenu(props: IndexerIndexFilterMenuProps) {
+function IndexerIndexFilterMenu(props) {
const {
selectedFilterKey,
filters,
@@ -34,6 +26,15 @@ function IndexerIndexFilterMenu(props: IndexerIndexFilterMenuProps) {
);
}
+IndexerIndexFilterMenu.propTypes = {
+ selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
+ .isRequired,
+ filters: PropTypes.arrayOf(PropTypes.object).isRequired,
+ customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
+ isDisabled: PropTypes.bool.isRequired,
+ onFilterSelect: PropTypes.func.isRequired,
+};
+
IndexerIndexFilterMenu.defaultProps = {
showCustomFilters: false,
};
diff --git a/frontend/src/Indexer/Index/Menus/IndexerIndexSortMenu.tsx b/frontend/src/Indexer/Index/Menus/IndexerIndexSortMenu.tsx
index 088cbca90..723db799f 100644
--- a/frontend/src/Indexer/Index/Menus/IndexerIndexSortMenu.tsx
+++ b/frontend/src/Indexer/Index/Menus/IndexerIndexSortMenu.tsx
@@ -1,19 +1,12 @@
+import PropTypes from 'prop-types';
import React from 'react';
import MenuContent from 'Components/Menu/MenuContent';
import SortMenu from 'Components/Menu/SortMenu';
import SortMenuItem from 'Components/Menu/SortMenuItem';
-import { align } from 'Helpers/Props';
-import SortDirection from 'Helpers/Props/SortDirection';
+import { align, sortDirections } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
-interface IndexerIndexSortMenuProps {
- sortKey?: string;
- sortDirection?: SortDirection;
- isDisabled: boolean;
- onSortSelect(sortKey: string): unknown;
-}
-
-function IndexerIndexSortMenu(props: IndexerIndexSortMenuProps) {
+function IndexerIndexSortMenu(props) {
const { sortKey, sortDirection, isDisabled, onSortSelect } = props;
return (
@@ -86,4 +79,11 @@ function IndexerIndexSortMenu(props: IndexerIndexSortMenuProps) {
);
}
+IndexerIndexSortMenu.propTypes = {
+ sortKey: PropTypes.string,
+ sortDirection: PropTypes.oneOf(sortDirections.all),
+ isDisabled: PropTypes.bool.isRequired,
+ onSortSelect: PropTypes.func.isRequired,
+};
+
export default IndexerIndexSortMenu;
diff --git a/frontend/src/Indexer/Index/Select/Delete/DeleteIndexerModalContent.tsx b/frontend/src/Indexer/Index/Select/Delete/DeleteIndexerModalContent.tsx
index 0793af82d..0e27902fe 100644
--- a/frontend/src/Indexer/Index/Select/Delete/DeleteIndexerModalContent.tsx
+++ b/frontend/src/Indexer/Index/Select/Delete/DeleteIndexerModalContent.tsx
@@ -7,7 +7,6 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { kinds } from 'Helpers/Props';
-import Indexer from 'Indexer/Indexer';
import { bulkDeleteIndexers } from 'Store/Actions/indexerActions';
import createAllIndexersSelector from 'Store/Selectors/createAllIndexersSelector';
import translate from 'Utilities/String/translate';
@@ -21,15 +20,15 @@ interface DeleteIndexerModalContentProps {
function DeleteIndexerModalContent(props: DeleteIndexerModalContentProps) {
const { indexerIds, onModalClose } = props;
- const allIndexers: Indexer[] = useSelector(createAllIndexersSelector());
+ const allIndexers = useSelector(createAllIndexersSelector());
const dispatch = useDispatch();
- const indexers = useMemo((): Indexer[] => {
- const indexerList = indexerIds.map((id) => {
+ const selectedIndexers = useMemo(() => {
+ const indexers = indexerIds.map((id) => {
return allIndexers.find((s) => s.id === id);
- }) as Indexer[];
+ });
- return orderBy(indexerList, ['sortName']);
+ return orderBy(indexers, ['sortName']);
}, [indexerIds, allIndexers]);
const onDeleteIndexerConfirmed = useCallback(() => {
@@ -48,13 +47,13 @@ function DeleteIndexerModalContent(props: DeleteIndexerModalContentProps) {
- {translate('DeleteSelectedIndexersMessageText', {
- count: indexers.length,
- })}
+ {translate('DeleteSelectedIndexersMessageText', [
+ selectedIndexers.length,
+ ])}
- {indexers.map((s) => {
+ {selectedIndexers.map((s) => {
return (
-
{s.name}
diff --git a/frontend/src/Indexer/Index/Select/Edit/EditIndexerModalContent.tsx b/frontend/src/Indexer/Index/Select/Edit/EditIndexerModalContent.tsx
index 9d42aa389..f3bb9cca7 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 {
@@ -31,25 +30,9 @@ interface EditIndexerModalContentProps {
const NO_CHANGE = 'noChange';
const enableOptions = [
- {
- key: NO_CHANGE,
- get value() {
- return translate('NoChange');
- },
- isDisabled: true,
- },
- {
- key: 'true',
- get value() {
- return translate('Enabled');
- },
- },
- {
- key: 'false',
- get value() {
- return translate('Disabled');
- },
- },
+ { key: NO_CHANGE, value: translate('NoChange'), disabled: true },
+ { key: 'true', value: translate('Enabled') },
+ { key: 'false', value: translate('Disabled') },
];
function EditIndexerModalContent(props: EditIndexerModalContentProps) {
@@ -66,9 +49,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 +89,6 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
payload.packSeedTime = packSeedTime as number;
}
- if (preferMagnetUrl !== null) {
- hasChanges = true;
- payload.preferMagnetUrl = preferMagnetUrl === 'true';
- }
-
if (hasChanges) {
onSavePress(payload);
}
@@ -127,13 +102,12 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
seedRatio,
seedTime,
packSeedTime,
- preferMagnetUrl,
onSavePress,
onModalClose,
]);
const onInputChange = useCallback(
- ({ name, value }: { name: string; value: string }) => {
+ ({ name, value }) => {
switch (name) {
case 'enable':
setEnable(value);
@@ -156,9 +130,6 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
case 'packSeedTime':
setPackSeedTime(value);
break;
- case 'preferMagnetUrl':
- setPreferMagnetUrl(value);
- break;
default:
console.warn(`EditIndexersModalContent Unknown Input: '${name}'`);
}
@@ -237,7 +208,6 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
name="seedRatio"
value={seedRatio}
helpText={translate('SeedRatioHelpText')}
- isFloat={true}
onChange={onInputChange}
/>
@@ -267,23 +237,11 @@ function EditIndexerModalContent(props: EditIndexerModalContentProps) {
onChange={onInputChange}
/>
-
-
- {translate('PreferMagnetUrl')}
-
-
-
- {translate('CountIndexersSelected', { count: selectedCount })}
+ {translate('CountIndexersSelected', [selectedCount])}
diff --git a/frontend/src/Indexer/Index/Select/IndexerIndexSelectAllButton.tsx b/frontend/src/Indexer/Index/Select/IndexerIndexSelectAllButton.tsx
index d6fc776d6..bd7682018 100644
--- a/frontend/src/Indexer/Index/Select/IndexerIndexSelectAllButton.tsx
+++ b/frontend/src/Indexer/Index/Select/IndexerIndexSelectAllButton.tsx
@@ -7,7 +7,7 @@ import translate from 'Utilities/String/translate';
interface IndexerIndexSelectAllButtonProps {
label: string;
isSelectMode: boolean;
- overflowComponent: React.FunctionComponent;
+ overflowComponent: React.FunctionComponent;
}
function IndexerIndexSelectAllButton(props: IndexerIndexSelectAllButtonProps) {
diff --git a/frontend/src/Indexer/Index/Select/IndexerIndexSelectFooter.tsx b/frontend/src/Indexer/Index/Select/IndexerIndexSelectFooter.tsx
index 64fe8c1cb..953d0daf9 100644
--- a/frontend/src/Indexer/Index/Select/IndexerIndexSelectFooter.tsx
+++ b/frontend/src/Indexer/Index/Select/IndexerIndexSelectFooter.tsx
@@ -15,16 +15,6 @@ import EditIndexerModal from './Edit/EditIndexerModal';
import TagsModal from './Tags/TagsModal';
import styles from './IndexerIndexSelectFooter.css';
-interface SavePayload {
- enable?: boolean;
- appProfileId?: number;
- priority?: number;
- minimumSeeders?: number;
- seedRatio?: number;
- seedTime?: number;
- packSeedTime?: number;
-}
-
const indexersEditorSelector = createSelector(
(state: AppState) => state.indexers,
(indexers) => {
@@ -70,7 +60,7 @@ function IndexerIndexSelectFooter() {
}, [setIsEditModalOpen]);
const onSavePress = useCallback(
- (payload: SavePayload) => {
+ (payload) => {
setIsSavingIndexer(true);
setIsEditModalOpen(false);
@@ -93,7 +83,7 @@ function IndexerIndexSelectFooter() {
}, [setIsTagsModalOpen]);
const onApplyTagsPress = useCallback(
- (tags: number[], applyTags: string) => {
+ (tags, applyTags) => {
setIsSavingTags(true);
setIsTagsModalOpen(false);
@@ -165,7 +155,7 @@ function IndexerIndexSelectFooter() {
- {translate('CountIndexersSelected', { count: selectedCount })}
+ {translate('CountIndexersSelected', [selectedCount])}
;
+ overflowComponent: React.FunctionComponent;
onPress: () => void;
}
diff --git a/frontend/src/Indexer/Index/Select/Tags/TagsModalContent.tsx b/frontend/src/Indexer/Index/Select/Tags/TagsModalContent.tsx
index 1964d271c..964d9ad57 100644
--- a/frontend/src/Indexer/Index/Select/Tags/TagsModalContent.tsx
+++ b/frontend/src/Indexer/Index/Select/Tags/TagsModalContent.tsx
@@ -1,7 +1,6 @@
-import { uniq } from 'lodash';
+import { concat, uniq } from 'lodash';
import React, { useCallback, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
-import { Tag } from 'App/State/TagsAppState';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -13,7 +12,6 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, sizes } from 'Helpers/Props';
-import Indexer from 'Indexer/Indexer';
import createAllIndexersSelector from 'Store/Selectors/createAllIndexersSelector';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
import translate from 'Utilities/String/translate';
@@ -28,35 +26,29 @@ interface TagsModalContentProps {
function TagsModalContent(props: TagsModalContentProps) {
const { indexerIds, onModalClose, onApplyTagsPress } = props;
- const allIndexers: Indexer[] = useSelector(createAllIndexersSelector());
- const tagList: Tag[] = useSelector(createTagsSelector());
+ const allIndexers = useSelector(createAllIndexersSelector());
+ const tagList = useSelector(createTagsSelector());
const [tags, setTags] = useState([]);
const [applyTags, setApplyTags] = useState('add');
const indexerTags = useMemo(() => {
- const tags = indexerIds.reduce((acc: number[], id) => {
- const s = allIndexers.find((s) => s.id === id);
+ const indexers = indexerIds.map((id) => {
+ return allIndexers.find((s) => s.id === id);
+ });
- if (s) {
- acc.push(...s.tags);
- }
-
- return acc;
- }, []);
-
- return uniq(tags);
+ return uniq(concat(...indexers.map((s) => s.tags)));
}, [indexerIds, allIndexers]);
const onTagsChange = useCallback(
- ({ value }: { value: number[] }) => {
+ ({ value }) => {
setTags(value);
},
[setTags]
);
const onApplyTagsChange = useCallback(
- ({ value }: { value: string }) => {
+ ({ value }) => {
setApplyTags(value);
},
[setApplyTags]
diff --git a/frontend/src/Indexer/Index/Table/CapabilitiesLabel.tsx b/frontend/src/Indexer/Index/Table/CapabilitiesLabel.tsx
index 8e30532cc..5f742d902 100644
--- a/frontend/src/Indexer/Index/Table/CapabilitiesLabel.tsx
+++ b/frontend/src/Indexer/Index/Table/CapabilitiesLabel.tsx
@@ -1,8 +1,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;
@@ -25,21 +23,17 @@ function CapabilitiesLabel(props: CapabilitiesLabelProps) {
);
}
- const indexerCategories = uniqBy(filteredList, 'id').sort(
- (a, b) => a.id - b.id
+ const nameList = Array.from(
+ new Set(filteredList.map((item) => item.name).sort())
);
return (
- {indexerCategories.map((category) => {
- return (
-
- );
+ {nameList.map((category) => {
+ return ;
})}
- {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..a0a0daee4 100644
--- a/frontend/src/Indexer/Index/Table/IndexerIndexRow.css
+++ b/frontend/src/Indexer/Index/Table/IndexerIndexRow.css
@@ -11,12 +11,6 @@
flex: 0 0 60px;
}
-.id {
- composes: cell;
-
- flex: 0 0 60px;
-}
-
.sortName {
composes: cell;
@@ -29,8 +23,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..c5d22cf6d 100644
--- a/frontend/src/Indexer/Index/Table/IndexerIndexRow.css.d.ts
+++ b/frontend/src/Indexer/Index/Table/IndexerIndexRow.css.d.ts
@@ -8,10 +8,8 @@ interface CssExports {
'cell': string;
'checkInput': string;
'externalLink': string;
- '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..5325028e9 100644
--- a/frontend/src/Indexer/Index/Table/IndexerIndexRow.tsx
+++ b/frontend/src/Indexer/Index/Table/IndexerIndexRow.tsx
@@ -1,9 +1,9 @@
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 RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
import Column from 'Components/Table/Column';
@@ -12,13 +12,11 @@ import { icons } from 'Helpers/Props';
import DeleteIndexerModal from 'Indexer/Delete/DeleteIndexerModal';
import EditIndexerModalConnector from 'Indexer/Edit/EditIndexerModalConnector';
import createIndexerIndexItemSelector from 'Indexer/Index/createIndexerIndexItemSelector';
-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';
@@ -27,14 +25,13 @@ interface IndexerIndexRowProps {
sortKey: string;
columns: Column[];
isSelectMode: boolean;
- onCloneIndexerPress(id: number): void;
}
function IndexerIndexRow(props: IndexerIndexRowProps) {
- const { indexerId, columns, isSelectMode, onCloneIndexerPress } = props;
+ const { indexerId, columns, isSelectMode } = props;
const { indexer, appProfile, status, longDateFormat, timeFormat } =
- useSelector(createIndexerIndexItemSelector(indexerId));
+ useSelector(createIndexerIndexItemSelector(props.indexerId));
const {
id,
@@ -49,7 +46,7 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
fields,
added,
capabilities,
- } = indexer as Indexer;
+ } = indexer;
const baseUrl =
fields.find((field) => field.name === 'baseUrl')?.value ??
@@ -75,10 +72,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(
@@ -112,7 +105,7 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
}, []);
const onSelectedChange = useCallback(
- ({ id, value, shiftKey }: SelectStateInputProps) => {
+ ({ id, value, shiftKey }) => {
selectDispatch({
type: 'toggleSelected',
id,
@@ -156,25 +149,12 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
);
}
- if (name === 'id') {
- return (
-
-
-
- );
- }
-
if (name === 'sortName') {
return (
);
@@ -183,7 +163,7 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
if (name === 'privacy') {
return (
-
+
);
}
@@ -222,9 +202,7 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
if (name === 'added') {
return (
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore ts(2739)
-
- {preferMagnetUrl === undefined ? null : (
-
- )}
-
- );
- }
-
if (name === 'actions') {
return (
;
+ scrollerRef: React.MutableRefObject;
isSelectMode: boolean;
isSmallScreen: boolean;
- onCloneIndexerPress(id: number): void;
}
const columnsSelector = createSelector(
- (state: AppState) => state.indexerIndex.columns,
+ (state) => state.indexerIndex.columns,
(columns) => columns
);
-function Row({ index, style, data }: ListChildComponentProps) {
- const { items, sortKey, columns, isSelectMode, onCloneIndexerPress } = data;
+const Row: React.FC> = ({
+ index,
+ style,
+ data,
+}) => {
+ const { items, sortKey, columns, isSelectMode } = data;
if (index >= items.length) {
return null;
@@ -62,18 +64,16 @@ function Row({ index, style, data }: ListChildComponentProps) {
justifyContent: 'space-between',
...style,
}}
- className={styles.row}
>
);
-}
+};
function getWindowScrollTopPosition() {
return document.documentElement.scrollTop || document.body.scrollTop || 0;
@@ -88,25 +88,25 @@ function IndexerIndexTable(props: IndexerIndexTableProps) {
isSelectMode,
isSmallScreen,
scrollerRef,
- onCloneIndexerPress,
} = props;
const columns = useSelector(columnsSelector);
- const listRef = useRef