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:
Mark McDowall 2019-02-06 19:25:01 -08:00
parent a29b1259b5
commit 807cfebf76
7 changed files with 154 additions and 51 deletions

View File

@ -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}
/> />
); );
} }

View File

@ -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,

View File

@ -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}
/> />

View File

@ -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);

View File

@ -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;

View File

@ -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
}; };
} }
); );

View File

@ -0,0 +1,5 @@
export default function roundNumber(input, decimalPlaces = 1) {
const multiplier = Math.pow(10, decimalPlaces);
return Math.round(input * multiplier) / multiplier;
}