mirror of https://github.com/Sonarr/Sonarr
Number input and max release size limit increased
Fixed: Number input changing value while typing New: Maximum size limit has been doubled Closes #2921
This commit is contained in:
parent
a29b1259b5
commit
807cfebf76
|
@ -2,34 +2,18 @@ import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import TextInput from './TextInput';
|
import TextInput from './TextInput';
|
||||||
|
|
||||||
class NumberInput extends Component {
|
function parseValue(props, value) {
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onChange = ({ name, value }) => {
|
|
||||||
let newValue = null;
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
newValue = this.props.isFloat ? parseFloat(value) : parseInt(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.onChange({
|
|
||||||
name,
|
|
||||||
value: newValue
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onBlur = () => {
|
|
||||||
const {
|
const {
|
||||||
name,
|
isFloat,
|
||||||
value,
|
|
||||||
min,
|
min,
|
||||||
max,
|
max
|
||||||
onChange
|
} = props;
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
let newValue = value;
|
if (value == null) {
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newValue = isFloat ? parseFloat(value) : parseInt(value);
|
||||||
|
|
||||||
if (min != null && newValue != null && newValue < min) {
|
if (min != null && newValue != null && newValue < min) {
|
||||||
newValue = min;
|
newValue = min;
|
||||||
|
@ -37,9 +21,67 @@ class NumberInput extends Component {
|
||||||
newValue = max;
|
newValue = max;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
class NumberInput extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
value: props.value.toString(),
|
||||||
|
isFocused: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps, prevState) {
|
||||||
|
if (this.props.value !== prevProps.value && !this.state.isFocused) {
|
||||||
|
this.setState({ value: this.props.value.toString() });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onChange = ({ name, value }) => {
|
||||||
|
this.setState({ value });
|
||||||
|
|
||||||
|
this.props.onChange({
|
||||||
|
name,
|
||||||
|
value: parseValue(this.props, value)
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
onFocus = () => {
|
||||||
|
this.setState({ isFocused: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
onBlur = () => {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
onChange
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const { value } = this.state;
|
||||||
|
const parsedValue = parseValue(this.props, value);
|
||||||
|
|
||||||
|
if (parsedValue.toString() === value) {
|
||||||
|
this.setState({ isFocused: false });
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
value: parsedValue.toString(),
|
||||||
|
isFocused: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onChange({
|
onChange({
|
||||||
name,
|
name,
|
||||||
value: newValue
|
value: parseValue(this.props, this.state.value)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,18 +89,16 @@ class NumberInput extends Component {
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const value = this.state.value;
|
||||||
value,
|
|
||||||
...otherProps
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TextInput
|
<TextInput
|
||||||
|
{...this.props}
|
||||||
type="number"
|
type="number"
|
||||||
value={value == null ? '' : value}
|
value={value == null ? '' : value}
|
||||||
{...otherProps}
|
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
onBlur={this.onBlur}
|
onBlur={this.onBlur}
|
||||||
|
onFocus={this.onFocus}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,6 +127,7 @@ class TextInput extends Component {
|
||||||
hasError,
|
hasError,
|
||||||
hasWarning,
|
hasWarning,
|
||||||
hasButton,
|
hasButton,
|
||||||
|
step,
|
||||||
onBlur
|
onBlur
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
@ -146,6 +147,7 @@ class TextInput extends Component {
|
||||||
)}
|
)}
|
||||||
name={name}
|
name={name}
|
||||||
value={value}
|
value={value}
|
||||||
|
step={step}
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
onFocus={this.onFocus}
|
onFocus={this.onFocus}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
|
@ -168,6 +170,7 @@ TextInput.propTypes = {
|
||||||
hasError: PropTypes.bool,
|
hasError: PropTypes.bool,
|
||||||
hasWarning: PropTypes.bool,
|
hasWarning: PropTypes.bool,
|
||||||
hasButton: PropTypes.bool,
|
hasButton: PropTypes.bool,
|
||||||
|
step: PropTypes.number,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
onFocus: PropTypes.func,
|
onFocus: PropTypes.func,
|
||||||
onBlur: PropTypes.func,
|
onBlur: PropTypes.func,
|
||||||
|
|
|
@ -2,28 +2,38 @@ import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import ReactSlider from 'react-slider';
|
import ReactSlider from 'react-slider';
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
|
import roundNumber from 'Utilities/Number/roundNumber';
|
||||||
import { kinds } from 'Helpers/Props';
|
import { kinds } from 'Helpers/Props';
|
||||||
import Label from 'Components/Label';
|
import Label from 'Components/Label';
|
||||||
import NumberInput from 'Components/Form/NumberInput';
|
import NumberInput from 'Components/Form/NumberInput';
|
||||||
import TextInput from 'Components/Form/TextInput';
|
import TextInput from 'Components/Form/TextInput';
|
||||||
import styles from './QualityDefinition.css';
|
import styles from './QualityDefinition.css';
|
||||||
|
|
||||||
|
const MIN = 0;
|
||||||
|
const MAX = 400;
|
||||||
|
|
||||||
const slider = {
|
const slider = {
|
||||||
min: 0,
|
min: MIN,
|
||||||
max: 200,
|
max: roundNumber(Math.pow(MAX, 1 / 1.1)),
|
||||||
step: 0.1
|
step: 0.1
|
||||||
};
|
};
|
||||||
|
|
||||||
function getValue(value) {
|
function getValue(inputValue) {
|
||||||
if (value < slider.min) {
|
if (inputValue < MIN) {
|
||||||
return slider.min;
|
return MIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value > slider.max) {
|
if (inputValue > MAX) {
|
||||||
return slider.max;
|
return MAX;
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return roundNumber(inputValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSliderValue(value, defaultValue) {
|
||||||
|
const sliderValue = value ? Math.pow(value, 1 / 1.1) : defaultValue;
|
||||||
|
|
||||||
|
return roundNumber(sliderValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
class QualityDefinition extends Component {
|
class QualityDefinition extends Component {
|
||||||
|
@ -35,6 +45,11 @@ class QualityDefinition extends Component {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
this._forceUpdateTimeout = null;
|
this._forceUpdateTimeout = null;
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
sliderMinSize: getSliderValue(props.minSize, slider.min),
|
||||||
|
sliderMaxSize: getSliderValue(props.maxSize, slider.max)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -54,15 +69,37 @@ class QualityDefinition extends Component {
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onSizeChange = ([minSize, maxSize]) => {
|
onSliderChange = ([sliderMinSize, sliderMaxSize]) => {
|
||||||
maxSize = maxSize === slider.max ? null : maxSize;
|
this.setState({
|
||||||
|
sliderMinSize,
|
||||||
|
sliderMaxSize
|
||||||
|
});
|
||||||
|
|
||||||
this.props.onSizeChange({ minSize, maxSize });
|
this.props.onSizeChange({
|
||||||
|
minSize: roundNumber(Math.pow(sliderMinSize, 1.1)),
|
||||||
|
maxSize: sliderMaxSize === slider.max ? null : roundNumber(Math.pow(sliderMaxSize, 1.1))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onAfterSliderChange = () => {
|
||||||
|
const {
|
||||||
|
minSize,
|
||||||
|
maxSize
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
sliderMiSize: getSliderValue(minSize, slider.min),
|
||||||
|
sliderMaxSize: getSliderValue(maxSize, slider.max)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onMinSizeChange = ({ value }) => {
|
onMinSizeChange = ({ value }) => {
|
||||||
const minSize = getValue(value);
|
const minSize = getValue(value);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
sliderMinSize: getSliderValue(minSize, slider.min)
|
||||||
|
});
|
||||||
|
|
||||||
this.props.onSizeChange({
|
this.props.onSizeChange({
|
||||||
minSize,
|
minSize,
|
||||||
maxSize: this.props.maxSize
|
maxSize: this.props.maxSize
|
||||||
|
@ -70,7 +107,11 @@ class QualityDefinition extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
onMaxSizeChange = ({ value }) => {
|
onMaxSizeChange = ({ value }) => {
|
||||||
const maxSize = value === slider.max ? null : getValue(value);
|
const maxSize = value === MAX ? null : getValue(value);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
sliderMaxSize: getSliderValue(maxSize, slider.max)
|
||||||
|
});
|
||||||
|
|
||||||
this.props.onSizeChange({
|
this.props.onSizeChange({
|
||||||
minSize: this.props.minSize,
|
minSize: this.props.minSize,
|
||||||
|
@ -92,6 +133,11 @@ class QualityDefinition extends Component {
|
||||||
onTitleChange
|
onTitleChange
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
sliderMinSize,
|
||||||
|
sliderMaxSize
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
const minBytes = minSize * 1024 * 1024;
|
const minBytes = minSize * 1024 * 1024;
|
||||||
const minThirty = formatBytes(minBytes * 30, 2);
|
const minThirty = formatBytes(minBytes * 30, 2);
|
||||||
const minSixty = formatBytes(minBytes * 60, 2);
|
const minSixty = formatBytes(minBytes * 60, 2);
|
||||||
|
@ -120,13 +166,14 @@ class QualityDefinition extends Component {
|
||||||
max={slider.max}
|
max={slider.max}
|
||||||
step={slider.step}
|
step={slider.step}
|
||||||
minDistance={10}
|
minDistance={10}
|
||||||
value={[minSize || slider.min, maxSize || slider.max]}
|
value={[sliderMinSize, sliderMaxSize]}
|
||||||
withBars={true}
|
withBars={true}
|
||||||
snapDragDisabled={true}
|
snapDragDisabled={true}
|
||||||
className={styles.slider}
|
className={styles.slider}
|
||||||
barClassName={styles.bar}
|
barClassName={styles.bar}
|
||||||
handleClassName={styles.handle}
|
handleClassName={styles.handle}
|
||||||
onChange={this.onSizeChange}
|
onChange={this.onSliderChange}
|
||||||
|
onAfterChange={this.onAfterSliderChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={styles.sizes}>
|
<div className={styles.sizes}>
|
||||||
|
@ -151,9 +198,10 @@ class QualityDefinition extends Component {
|
||||||
<NumberInput
|
<NumberInput
|
||||||
className={styles.sizeInput}
|
className={styles.sizeInput}
|
||||||
name={`${id}.min`}
|
name={`${id}.min`}
|
||||||
value={minSize || slider.min}
|
value={minSize || MIN}
|
||||||
min={slider.min}
|
min={MIN}
|
||||||
max={maxSize ? maxSize - 10 : slider.max - 10}
|
max={maxSize ? maxSize - 10 : MAX - 10}
|
||||||
|
step={0.1}
|
||||||
isFloat={true}
|
isFloat={true}
|
||||||
onChange={this.onMinSizeChange}
|
onChange={this.onMinSizeChange}
|
||||||
/>
|
/>
|
||||||
|
@ -165,9 +213,10 @@ class QualityDefinition extends Component {
|
||||||
<NumberInput
|
<NumberInput
|
||||||
className={styles.sizeInput}
|
className={styles.sizeInput}
|
||||||
name={`${id}.min`}
|
name={`${id}.min`}
|
||||||
value={maxSize || slider.max}
|
value={maxSize || MAX}
|
||||||
min={minSize + 10}
|
min={minSize + 10}
|
||||||
max={slider.max}
|
max={MAX}
|
||||||
|
step={0.1}
|
||||||
isFloat={true}
|
isFloat={true}
|
||||||
onChange={this.onMaxSizeChange}
|
onChange={this.onMaxSizeChange}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -5,12 +5,6 @@ import { setQualityDefinitionValue } from 'Store/Actions/settingsActions';
|
||||||
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||||
import QualityDefinition from './QualityDefinition';
|
import QualityDefinition from './QualityDefinition';
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
|
||||||
return {
|
|
||||||
advancedSettings: state.settings.advancedSettings
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
setQualityDefinitionValue,
|
setQualityDefinitionValue,
|
||||||
clearPendingChanges
|
clearPendingChanges
|
||||||
|
@ -40,7 +34,7 @@ class QualityDefinitionConnector extends Component {
|
||||||
this.props.setQualityDefinitionValue({ id, name: 'minSize', value: minSize });
|
this.props.setQualityDefinitionValue({ id, name: 'minSize', value: minSize });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (minSize !== currentMaxSize) {
|
if (maxSize !== currentMaxSize) {
|
||||||
this.props.setQualityDefinitionValue({ id, name: 'maxSize', value: maxSize });
|
this.props.setQualityDefinitionValue({ id, name: 'maxSize', value: maxSize });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,4 +61,4 @@ QualityDefinitionConnector.propTypes = {
|
||||||
clearPendingChanges: PropTypes.func.isRequired
|
clearPendingChanges: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(QualityDefinitionConnector);
|
export default connect(null, mapDispatchToProps)(QualityDefinitionConnector);
|
||||||
|
|
|
@ -13,6 +13,7 @@ class QualityDefinitions extends Component {
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
|
advancedSettings,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
@ -26,7 +27,14 @@ class QualityDefinitions extends Component {
|
||||||
<div className={styles.quality}>Quality</div>
|
<div className={styles.quality}>Quality</div>
|
||||||
<div className={styles.title}>Title</div>
|
<div className={styles.title}>Title</div>
|
||||||
<div className={styles.sizeLimit}>Size Limit</div>
|
<div className={styles.sizeLimit}>Size Limit</div>
|
||||||
<div className={styles.megabytesPerMinute}>Megabytes Per Minute</div>
|
|
||||||
|
{
|
||||||
|
advancedSettings ?
|
||||||
|
<div className={styles.megabytesPerMinute}>
|
||||||
|
Megabytes Per Minute
|
||||||
|
</div> :
|
||||||
|
null
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.definitions}>
|
<div className={styles.definitions}>
|
||||||
|
@ -36,6 +44,7 @@ class QualityDefinitions extends Component {
|
||||||
<QualityDefinitionConnector
|
<QualityDefinitionConnector
|
||||||
key={item.id}
|
key={item.id}
|
||||||
{...item}
|
{...item}
|
||||||
|
advancedSettings={advancedSettings }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
@ -57,7 +66,8 @@ QualityDefinitions.propTypes = {
|
||||||
isFetching: PropTypes.bool.isRequired,
|
isFetching: PropTypes.bool.isRequired,
|
||||||
error: PropTypes.object,
|
error: PropTypes.object,
|
||||||
defaultProfile: PropTypes.object,
|
defaultProfile: PropTypes.object,
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
advancedSettings: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default QualityDefinitions;
|
export default QualityDefinitions;
|
||||||
|
|
|
@ -9,7 +9,8 @@ import QualityDefinitions from './QualityDefinitions';
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.settings.qualityDefinitions,
|
(state) => state.settings.qualityDefinitions,
|
||||||
(qualityDefinitions) => {
|
(state) => state.settings.advancedSettings,
|
||||||
|
(qualityDefinitions, advancedSettings) => {
|
||||||
const items = qualityDefinitions.items.map((item) => {
|
const items = qualityDefinitions.items.map((item) => {
|
||||||
const pendingChanges = qualityDefinitions.pendingChanges[item.id] || {};
|
const pendingChanges = qualityDefinitions.pendingChanges[item.id] || {};
|
||||||
|
|
||||||
|
@ -19,7 +20,8 @@ function createMapStateToProps() {
|
||||||
return {
|
return {
|
||||||
...qualityDefinitions,
|
...qualityDefinitions,
|
||||||
items,
|
items,
|
||||||
hasPendingChanges: !_.isEmpty(qualityDefinitions.pendingChanges)
|
hasPendingChanges: !_.isEmpty(qualityDefinitions.pendingChanges),
|
||||||
|
advancedSettings
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
export default function roundNumber(input, decimalPlaces = 1) {
|
||||||
|
const multiplier = Math.pow(10, decimalPlaces);
|
||||||
|
|
||||||
|
return Math.round(input * multiplier) / multiplier;
|
||||||
|
}
|
Loading…
Reference in New Issue