mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2025-09-17 17:14:18 +02:00
Fixed: Backend Updates from Sonarr
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com> Co-Authored-By: taloth <taloth@users.noreply.github.com>
This commit is contained in:
@@ -2,13 +2,13 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { fetchDevices, clearDevices } from 'Store/Actions/deviceActions';
|
||||
import { fetchOptions, clearOptions } from 'Store/Actions/providerOptionActions';
|
||||
import DeviceInput from './DeviceInput';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state, { value }) => value,
|
||||
(state) => state.devices,
|
||||
(state) => state.providerOptions,
|
||||
(value, devices) => {
|
||||
|
||||
return {
|
||||
@@ -37,8 +37,8 @@ function createMapStateToProps() {
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
dispatchFetchDevices: fetchDevices,
|
||||
dispatchClearDevices: clearDevices
|
||||
dispatchFetchOptions: fetchOptions,
|
||||
dispatchClearOptions: clearOptions
|
||||
};
|
||||
|
||||
class DeviceInputConnector extends Component {
|
||||
@@ -51,7 +51,7 @@ class DeviceInputConnector extends Component {
|
||||
}
|
||||
|
||||
componentWillUnmount = () => {
|
||||
// this.props.dispatchClearDevices();
|
||||
this.props.dispatchClearOptions();
|
||||
}
|
||||
|
||||
//
|
||||
@@ -61,10 +61,14 @@ class DeviceInputConnector extends Component {
|
||||
const {
|
||||
provider,
|
||||
providerData,
|
||||
dispatchFetchDevices
|
||||
dispatchFetchOptions
|
||||
} = this.props;
|
||||
|
||||
dispatchFetchDevices({ provider, providerData });
|
||||
dispatchFetchOptions({
|
||||
action: 'getDevices',
|
||||
provider,
|
||||
providerData
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
@@ -92,8 +96,8 @@ DeviceInputConnector.propTypes = {
|
||||
providerData: PropTypes.object.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
dispatchFetchDevices: PropTypes.func.isRequired,
|
||||
dispatchClearDevices: PropTypes.func.isRequired
|
||||
dispatchFetchOptions: PropTypes.func.isRequired,
|
||||
dispatchClearOptions: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(DeviceInputConnector);
|
||||
|
@@ -6,7 +6,7 @@ import classNames from 'classnames';
|
||||
import getUniqueElememtId from 'Utilities/getUniqueElementId';
|
||||
import isMobileUtil from 'Utilities/isMobile';
|
||||
import * as keyCodes from 'Utilities/Constants/keyCodes';
|
||||
import { icons, scrollDirections } from 'Helpers/Props';
|
||||
import { icons, sizes, scrollDirections } from 'Helpers/Props';
|
||||
import Icon from 'Components/Icon';
|
||||
import Portal from 'Components/Portal';
|
||||
import Link from 'Components/Link/Link';
|
||||
@@ -14,8 +14,8 @@ import Measure from 'Components/Measure';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import Scroller from 'Components/Scroller/Scroller';
|
||||
import EnhancedSelectInputSelectedValue from './EnhancedSelectInputSelectedValue';
|
||||
import EnhancedSelectInputOption from './EnhancedSelectInputOption';
|
||||
import HintedSelectInputSelectedValue from './HintedSelectInputSelectedValue';
|
||||
import HintedSelectInputOption from './HintedSelectInputOption';
|
||||
import styles from './EnhancedSelectInput.css';
|
||||
|
||||
function isArrowKey(keyCode) {
|
||||
@@ -150,9 +150,11 @@ class EnhancedSelectInput extends Component {
|
||||
}
|
||||
|
||||
onBlur = () => {
|
||||
this.setState({
|
||||
selectedIndex: getSelectedIndex(this.props)
|
||||
});
|
||||
// Calling setState without this check prevents the click event from being properly handled on Chrome (it is on firefox)
|
||||
const origIndex = getSelectedIndex(this.props);
|
||||
if (origIndex !== this.state.selectedIndex) {
|
||||
this.setState({ selectedIndex: origIndex });
|
||||
}
|
||||
}
|
||||
|
||||
onKeyDown = (event) => {
|
||||
@@ -385,6 +387,7 @@ class EnhancedSelectInput extends Component {
|
||||
isMobile &&
|
||||
<Modal
|
||||
className={styles.optionsModal}
|
||||
size={sizes.EXTRA_SMALL}
|
||||
isOpen={isOpen}
|
||||
onModalClose={this.onOptionsModalClose}
|
||||
>
|
||||
@@ -439,8 +442,8 @@ EnhancedSelectInput.defaultProps = {
|
||||
disabledClassName: styles.isDisabled,
|
||||
isDisabled: false,
|
||||
selectedValueOptions: {},
|
||||
selectedValueComponent: EnhancedSelectInputSelectedValue,
|
||||
optionComponent: EnhancedSelectInputOption
|
||||
selectedValueComponent: HintedSelectInputSelectedValue,
|
||||
optionComponent: HintedSelectInputOption
|
||||
};
|
||||
|
||||
export default EnhancedSelectInput;
|
||||
|
@@ -7,13 +7,17 @@
|
||||
cursor: default;
|
||||
|
||||
&:hover {
|
||||
background-color: #f9f9f9;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
}
|
||||
|
||||
.isSelected {
|
||||
background-color: #e2e2e2;
|
||||
|
||||
&:hover {
|
||||
background-color: #e2e2e2;
|
||||
}
|
||||
|
||||
&.isMobile {
|
||||
background-color: inherit;
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
.inputGroupContainer {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.inputGroup {
|
||||
@@ -11,6 +12,7 @@
|
||||
.inputContainer {
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.inputUnit {
|
||||
|
@@ -14,7 +14,7 @@ import PathInputConnector from './PathInputConnector';
|
||||
import QualityProfileSelectInputConnector from './QualityProfileSelectInputConnector';
|
||||
import RootFolderSelectInputConnector from './RootFolderSelectInputConnector';
|
||||
import MovieMonitoredSelectInput from './MovieMonitoredSelectInput';
|
||||
import SelectInput from './SelectInput';
|
||||
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||
import TagInputConnector from './TagInputConnector';
|
||||
import TextTagInputConnector from './TextTagInputConnector';
|
||||
import TextInput from './TextInput';
|
||||
@@ -60,7 +60,7 @@ function getComponent(type) {
|
||||
return RootFolderSelectInputConnector;
|
||||
|
||||
case inputTypes.SELECT:
|
||||
return SelectInput;
|
||||
return EnhancedSelectInput;
|
||||
|
||||
case inputTypes.TAG:
|
||||
return TagInputConnector;
|
||||
|
23
frontend/src/Components/Form/HintedSelectInputOption.css
Normal file
23
frontend/src/Components/Form/HintedSelectInputOption.css
Normal file
@@ -0,0 +1,23 @@
|
||||
.optionText {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex: 1 0 0;
|
||||
min-width: 0;
|
||||
|
||||
&.isMobile {
|
||||
display: block;
|
||||
|
||||
.hintText {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hintText {
|
||||
@add-mixin truncate;
|
||||
|
||||
margin-left: 15px;
|
||||
color: $darkGray;
|
||||
font-size: $smallFontSize;
|
||||
}
|
44
frontend/src/Components/Form/HintedSelectInputOption.js
Normal file
44
frontend/src/Components/Form/HintedSelectInputOption.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import EnhancedSelectInputOption from './EnhancedSelectInputOption';
|
||||
import styles from './HintedSelectInputOption.css';
|
||||
|
||||
function HintedSelectInputOption(props) {
|
||||
const {
|
||||
value,
|
||||
hint,
|
||||
isMobile,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<EnhancedSelectInputOption
|
||||
isMobile={isMobile}
|
||||
{...otherProps}
|
||||
>
|
||||
<div className={classNames(
|
||||
styles.optionText,
|
||||
isMobile && styles.isMobile
|
||||
)}
|
||||
>
|
||||
<div>{value}</div>
|
||||
|
||||
{
|
||||
hint != null &&
|
||||
<div className={styles.hintText}>
|
||||
{hint}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</EnhancedSelectInputOption>
|
||||
);
|
||||
}
|
||||
|
||||
HintedSelectInputOption.propTypes = {
|
||||
value: PropTypes.string.isRequired,
|
||||
hint: PropTypes.node,
|
||||
isMobile: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
export default HintedSelectInputOption;
|
@@ -0,0 +1,24 @@
|
||||
.selectedValue {
|
||||
composes: selectedValue from '~./EnhancedSelectInputSelectedValue.css';
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.valueText {
|
||||
@add-mixin truncate;
|
||||
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.hintText {
|
||||
@add-mixin truncate;
|
||||
|
||||
flex: 1 10 0;
|
||||
margin-left: 15px;
|
||||
color: $gray;
|
||||
text-align: right;
|
||||
font-size: $smallFontSize;
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import EnhancedSelectInputSelectedValue from './EnhancedSelectInputSelectedValue';
|
||||
import styles from './HintedSelectInputSelectedValue.css';
|
||||
|
||||
function HintedSelectInputSelectedValue(props) {
|
||||
const {
|
||||
value,
|
||||
hint,
|
||||
includeHint,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<EnhancedSelectInputSelectedValue
|
||||
className={styles.selectedValue}
|
||||
{...otherProps}
|
||||
>
|
||||
<div className={styles.valueText}>
|
||||
{value}
|
||||
</div>
|
||||
|
||||
{
|
||||
hint != null && includeHint &&
|
||||
<div className={styles.hintText}>
|
||||
{hint}
|
||||
</div>
|
||||
}
|
||||
</EnhancedSelectInputSelectedValue>
|
||||
);
|
||||
}
|
||||
|
||||
HintedSelectInputSelectedValue.propTypes = {
|
||||
value: PropTypes.string,
|
||||
hint: PropTypes.string,
|
||||
includeHint: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
HintedSelectInputSelectedValue.defaultProps = {
|
||||
includeHint: true
|
||||
};
|
||||
|
||||
export default HintedSelectInputSelectedValue;
|
@@ -20,7 +20,7 @@ function getType(type) {
|
||||
return inputTypes.NUMBER;
|
||||
case 'path':
|
||||
return inputTypes.PATH;
|
||||
case 'filepath':
|
||||
case 'filePath':
|
||||
return inputTypes.PATH;
|
||||
case 'select':
|
||||
return inputTypes.SELECT;
|
||||
@@ -60,6 +60,7 @@ function ProviderFieldFormGroup(props) {
|
||||
value,
|
||||
type,
|
||||
advanced,
|
||||
hidden,
|
||||
pending,
|
||||
errors,
|
||||
warnings,
|
||||
@@ -68,6 +69,13 @@ function ProviderFieldFormGroup(props) {
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
if (
|
||||
hidden === 'hidden' ||
|
||||
(hidden === 'hiddenIfNotSet' && !value)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
@@ -86,7 +94,7 @@ function ProviderFieldFormGroup(props) {
|
||||
errors={errors}
|
||||
warnings={warnings}
|
||||
pending={pending}
|
||||
includeFiles={type === 'filepath' ? true : undefined}
|
||||
includeFiles={type === 'filePath' ? true : undefined}
|
||||
onChange={onChange}
|
||||
{...otherProps}
|
||||
/>
|
||||
@@ -108,6 +116,7 @@ ProviderFieldFormGroup.propTypes = {
|
||||
value: PropTypes.any,
|
||||
type: PropTypes.string.isRequired,
|
||||
advanced: PropTypes.bool.isRequired,
|
||||
hidden: PropTypes.string,
|
||||
pending: PropTypes.bool.isRequired,
|
||||
errors: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
warnings: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
|
@@ -1,7 +1,6 @@
|
||||
.input {
|
||||
composes: input from '~./AutoSuggestInput.css';
|
||||
|
||||
position: relative;
|
||||
padding: 0;
|
||||
min-height: 35px;
|
||||
height: auto;
|
||||
|
@@ -1,5 +1,4 @@
|
||||
.inputContainer {
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
right: -1px;
|
||||
bottom: -1px;
|
||||
|
@@ -128,6 +128,8 @@ class TextInput extends Component {
|
||||
hasWarning,
|
||||
hasButton,
|
||||
step,
|
||||
min,
|
||||
max,
|
||||
onBlur
|
||||
} = this.props;
|
||||
|
||||
@@ -148,6 +150,8 @@ class TextInput extends Component {
|
||||
name={name}
|
||||
value={value}
|
||||
step={step}
|
||||
min={min}
|
||||
max={max}
|
||||
onChange={this.onChange}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={onBlur}
|
||||
@@ -171,6 +175,8 @@ TextInput.propTypes = {
|
||||
hasWarning: PropTypes.bool,
|
||||
hasButton: PropTypes.bool,
|
||||
step: PropTypes.number,
|
||||
min: PropTypes.number,
|
||||
max: PropTypes.number,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onFocus: PropTypes.func,
|
||||
onBlur: PropTypes.func,
|
||||
|
@@ -47,7 +47,7 @@ class Link extends Component {
|
||||
el = 'a';
|
||||
linkProps.href = to;
|
||||
linkProps.target = target || '_self';
|
||||
} else if (to.startsWith(window.Radarr.urlBase)) {
|
||||
} else if (to.startsWith(`${window.Radarr.urlBase}/`)) {
|
||||
el = RouterLink;
|
||||
linkProps.to = to;
|
||||
linkProps.target = target;
|
||||
|
@@ -154,8 +154,33 @@ class MovieSearchInput extends Component {
|
||||
}
|
||||
|
||||
onSuggestionsFetchRequested = ({ value }) => {
|
||||
const fuse = new Fuse(this.props.movies, fuseOptions);
|
||||
const suggestions = fuse.search(value);
|
||||
const { movies } = this.props;
|
||||
let suggestions = [];
|
||||
|
||||
if (value.length === 1) {
|
||||
suggestions = movies.reduce((acc, s) => {
|
||||
if (s.firstCharacter === value.toLowerCase()) {
|
||||
acc.push({
|
||||
item: s,
|
||||
indices: [
|
||||
[0, 0]
|
||||
],
|
||||
matches: [
|
||||
{
|
||||
value: s.title,
|
||||
key: 'title'
|
||||
}
|
||||
],
|
||||
arrayIndex: 0
|
||||
});
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
} else {
|
||||
const fuse = new Fuse(movies, fuseOptions);
|
||||
suggestions = fuse.search(value);
|
||||
}
|
||||
|
||||
this.setState({ suggestions });
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ import { connect } from 'react-redux';
|
||||
import { push } from 'connected-react-router';
|
||||
import { createSelector } from 'reselect';
|
||||
import createAllMoviesSelector from 'Store/Selectors/createAllMoviesSelector';
|
||||
import createDeepEqualSelector from 'Store/Selectors/createDeepEqualSelector';
|
||||
import createTagsSelector from 'Store/Selectors/createTagsSelector';
|
||||
import MovieSearchInput from './MovieSearchInput';
|
||||
|
||||
@@ -26,9 +27,16 @@ function createCleanMovieSelector() {
|
||||
sortTitle,
|
||||
images,
|
||||
alternateTitles,
|
||||
tags: tags.map((id) => {
|
||||
return allTags.find((tag) => tag.id === id);
|
||||
})
|
||||
firstCharacter: title.charAt(0).toLowerCase(),
|
||||
tags: tags.reduce((acc, id) => {
|
||||
const matchingTag = allTags.find((tag) => tag.id === id);
|
||||
|
||||
if (matchingTag) {
|
||||
acc.push(matchingTag);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, [])
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -36,7 +44,7 @@ function createCleanMovieSelector() {
|
||||
}
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
return createDeepEqualSelector(
|
||||
createCleanMovieSelector(),
|
||||
(movies) => {
|
||||
return {
|
||||
|
@@ -84,7 +84,7 @@ class SignalRConnector extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.signalRconnectionOptions = { transport: ['webSockets', 'longPolling'] };
|
||||
this.signalRconnectionOptions = { transport: ['webSockets', 'serverSentEvents', 'longPolling'] };
|
||||
this.signalRconnection = null;
|
||||
this.retryInterval = 1;
|
||||
this.retryTimeoutId = null;
|
||||
|
Reference in New Issue
Block a user