diff --git a/frontend/src/Components/Form/AppProfileSelectInputConnector.js b/frontend/src/Components/Form/AppProfileSelectInputConnector.js
index 0ab181e2f..fc40e9d3c 100644
--- a/frontend/src/Components/Form/AppProfileSelectInputConnector.js
+++ b/frontend/src/Components/Form/AppProfileSelectInputConnector.js
@@ -4,13 +4,12 @@ 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 translate from 'Utilities/String/translate';
+import sortByName from 'Utilities/Array/sortByName';
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 +23,16 @@ function createMapStateToProps() {
if (includeNoChange) {
values.unshift({
key: 'noChange',
- get value() {
- return translate('NoChange');
- },
- isDisabled: true
+ value: 'No Change',
+ 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
deleted file mode 100644
index 9cf7a429a..000000000
--- a/frontend/src/Components/Form/DownloadClientSelectInputConnector.js
+++ /dev/null
@@ -1,100 +0,0 @@
-import PropTypes from 'prop-types';
-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 EnhancedSelectInput from './EnhancedSelectInput';
-
-function createMapStateToProps() {
- return createSelector(
- (state) => state.settings.downloadClients,
- (state, { includeAny }) => includeAny,
- (state, { protocol }) => protocol,
- (downloadClients, includeAny, protocolFilter) => {
- const {
- isFetching,
- isPopulated,
- error,
- items
- } = downloadClients;
-
- const values = items
- .filter((downloadClient) => downloadClient.protocol === protocolFilter)
- .sort(sortByProp('name'))
- .map((downloadClient) => ({
- key: downloadClient.id,
- value: downloadClient.name,
- hint: `(${downloadClient.id})`
- }));
-
- if (includeAny) {
- values.unshift({
- key: 0,
- value: `(${translate('Any')})`
- });
- }
-
- return {
- isFetching,
- isPopulated,
- error,
- values
- };
- }
- );
-}
-
-const mapDispatchToProps = {
- dispatchFetchDownloadClients: fetchDownloadClients
-};
-
-class DownloadClientSelectInputConnector extends Component {
-
- //
- // Lifecycle
-
- componentDidMount() {
- if (!this.props.isPopulated) {
- this.props.dispatchFetchDownloadClients();
- }
- }
-
- //
- // Listeners
-
- onChange = ({ name, value }) => {
- this.props.onChange({ name, value: parseInt(value) });
- };
-
- //
- // Render
-
- render() {
- return (
-
- );
- }
-}
-
-DownloadClientSelectInputConnector.propTypes = {
- isFetching: PropTypes.bool.isRequired,
- isPopulated: PropTypes.bool.isRequired,
- name: PropTypes.string.isRequired,
- value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
- values: PropTypes.arrayOf(PropTypes.object).isRequired,
- includeAny: PropTypes.bool.isRequired,
- onChange: PropTypes.func.isRequired,
- dispatchFetchDownloadClients: PropTypes.func.isRequired
-};
-
-DownloadClientSelectInputConnector.defaultProps = {
- includeAny: false,
- protocol: 'torrent'
-};
-
-export default connect(createMapStateToProps, mapDispatchToProps)(DownloadClientSelectInputConnector);
diff --git a/frontend/src/Components/Form/EnhancedSelectInput.js b/frontend/src/Components/Form/EnhancedSelectInput.js
index 79b1c999c..4df54092c 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..f25946f59 100644
--- a/frontend/src/Components/Form/FormInputGroup.js
+++ b/frontend/src/Components/Form/FormInputGroup.js
@@ -5,11 +5,11 @@ 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';
import DeviceInputConnector from './DeviceInputConnector';
-import DownloadClientSelectInputConnector from './DownloadClientSelectInputConnector';
import EnhancedSelectInput from './EnhancedSelectInput';
import EnhancedSelectInputConnector from './EnhancedSelectInputConnector';
import FormInputHelpText from './FormInputHelpText';
@@ -36,6 +36,9 @@ function getComponent(type) {
case inputTypes.AUTO_COMPLETE:
return AutoCompleteInput;
+ case inputTypes.AVAILABILITY_SELECT:
+ return AvailabilitySelectInput;
+
case inputTypes.CAPTCHA:
return CaptchaInputConnector;
@@ -69,9 +72,6 @@ function getComponent(type) {
case inputTypes.CATEGORY_SELECT:
return NewznabCategorySelectInputConnector;
- case inputTypes.DOWNLOAD_CLIENT_SELECT:
- return DownloadClientSelectInputConnector;
-
case inputTypes.INDEXER_FLAGS_SELECT:
return IndexerFlagsSelectInputConnector;
@@ -256,18 +256,14 @@ 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,
- max: PropTypes.number,
unit: PropTypes.string,
buttons: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
helpText: PropTypes.string,
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..bc411d5cc 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 = _(indexers.items).groupBy((x) => x.protocol).map((val, key) => ({ protocol: key, indexers: val })).value();
groupedIndexers.forEach((element) => {
values.push({
@@ -23,12 +21,10 @@ function createMapStateToProps() {
});
if (element.indexers && element.indexers.length > 0) {
- element.indexers.forEach((indexer) => {
+ element.indexers.forEach((subCat) => {
values.push({
- key: indexer.id,
- value: indexer.name,
- hint: `(${indexer.id})`,
- isDisabled: !indexer.enable,
+ key: subCat.id,
+ value: subCat.name,
parentKey: element.protocol === 'usenet' ? -1 : -2
});
});
@@ -53,6 +49,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..8db1cd7b6 100644
--- a/frontend/src/Components/Form/NumberInput.js
+++ b/frontend/src/Components/Form/NumberInput.js
@@ -10,7 +10,7 @@ function parseValue(props, value) {
} = props;
if (value == null || value === '') {
- return null;
+ return min;
}
let newValue = isFloat ? parseFloat(value) : parseInt(value);
@@ -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/ProviderFieldFormGroup.js b/frontend/src/Components/Form/ProviderFieldFormGroup.js
index 878e3a7ce..2b32e0e38 100644
--- a/frontend/src/Components/Form/ProviderFieldFormGroup.js
+++ b/frontend/src/Components/Form/ProviderFieldFormGroup.js
@@ -67,7 +67,6 @@ function ProviderFieldFormGroup(props) {
name,
label,
helpText,
- helpTextWarning,
helpLink,
placeholder,
value,
@@ -101,7 +100,6 @@ function ProviderFieldFormGroup(props) {
name={name}
label={label}
helpText={helpText}
- helpTextWarning={helpTextWarning}
helpLink={helpLink}
placeholder={placeholder}
value={value}
@@ -128,7 +126,6 @@ ProviderFieldFormGroup.propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.string,
helpText: PropTypes.string,
- helpTextWarning: PropTypes.string,
helpLink: PropTypes.string,
placeholder: PropTypes.string,
value: PropTypes.any,
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/Form/TagInputInput.css b/frontend/src/Components/Form/TagInputInput.css
index ab9d08d61..292f1a089 100644
--- a/frontend/src/Components/Form/TagInputInput.css
+++ b/frontend/src/Components/Form/TagInputInput.css
@@ -1,5 +1,8 @@
.inputContainer {
- inset: -1px;
+ top: -1px;
+ right: -1px;
+ bottom: -1px;
+ left: -1px;
display: flex;
align-items: start;
flex-wrap: wrap;
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/IconButton.js b/frontend/src/Components/Link/IconButton.js
index 6f5e56d0e..3311c14d6 100644
--- a/frontend/src/Components/Link/IconButton.js
+++ b/frontend/src/Components/Link/IconButton.js
@@ -23,7 +23,6 @@ function IconButton(props) {
className,
isDisabled && styles.isDisabled
)}
- aria-label="Table Options Button"
isDisabled={isDisabled}
{...otherProps}
>
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/Loading/LoadingMessage.js b/frontend/src/Components/Loading/LoadingMessage.js
index 406ecf030..1cb52b0aa 100644
--- a/frontend/src/Components/Loading/LoadingMessage.js
+++ b/frontend/src/Components/Loading/LoadingMessage.js
@@ -13,7 +13,7 @@ const messages = [
'Loading humorous message... Please Wait',
'I could\'ve been faster in Python',
'Don\'t forget to rewind your tracks',
- 'Congratulations! You are the 1000th visitor.',
+ 'Congratulations! you are the 1000th visitor.',
'HELP! I\'m being held hostage and forced to write these stupid lines!',
'RE-calibrating the internet...',
'I\'ll be here all week',
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..d9e2c1189 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 {
@@ -56,7 +56,6 @@ class PageHeader extends Component {
@@ -75,10 +74,8 @@ class PageHeader extends Component {