1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2026-04-25 22:46:31 -04:00
This commit is contained in:
Mark McDowall
2018-01-12 18:01:27 -08:00
committed by Taloth Saldono
parent 99feff549d
commit 5894b4fd95
1183 changed files with 91622 additions and 4978 deletions
@@ -0,0 +1,93 @@
.qualityDefinition {
display: flex;
align-content: stretch;
margin: 5px 0;
padding-top: 5px;
height: 45px;
border-top: 1px solid $borderColor;
}
.quality,
.title {
flex: 0 1 250px;
padding-right: 20px;
line-height: 40px;
}
.sizeLimit {
flex: 0 1 500px;
padding-right: 30px;
}
.slider {
width: 100%;
height: 20px;
}
.bar {
top: 9px;
margin: 0 5px;
height: 3px;
background-color: $sliderAccentColor;
box-shadow: 0 0 0 #000;
&:nth-child(odd) {
background-color: #ddd;
}
}
.handle {
top: 1px;
z-index: 0 !important;
width: 18px;
height: 18px;
border: 3px solid $sliderAccentColor;
border-radius: 50%;
background-color: $white;
text-align: center;
cursor: pointer;
}
.sizes {
display: flex;
justify-content: space-between;
}
.megabytesPerMinute {
display: flex;
justify-content: space-between;
flex: 0 0 250px;
}
.sizeInput {
composes: input from 'Components/Form/TextInput.css';
display: inline-block;
margin-left: 5px;
padding: 6px;
width: 75px;
}
@media only screen and (max-width: $breakpointSmall) {
.qualityDefinition {
flex-wrap: wrap;
height: auto;
&:first-child {
border-top: none;
}
}
.qualityDefinition:first-child {
border-top: none;
}
.quality {
font-weight: bold;
line-height: inherit;
}
.sizeLimit {
margin-top: 10px;
}
}
@@ -0,0 +1,193 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import ReactSlider from 'react-slider';
import formatBytes from 'Utilities/Number/formatBytes';
import { kinds } from 'Helpers/Props';
import Label from 'Components/Label';
import NumberInput from 'Components/Form/NumberInput';
import TextInput from 'Components/Form/TextInput';
import styles from './QualityDefinition.css';
const slider = {
min: 0,
max: 200,
step: 0.1
};
function getValue(value) {
if (value < slider.min) {
return slider.min;
}
if (value > slider.max) {
return slider.max;
}
return value;
}
class QualityDefinition extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this._forceUpdateTimeout = null;
}
componentDidMount() {
// A hack to deal with a bug in the slider component until a fix for it
// lands and an updated version is available.
// See: https://github.com/mpowaga/react-slider/issues/115
this._forceUpdateTimeout = setTimeout(() => this.forceUpdate(), 1);
}
componentWillUnmount() {
if (this._forceUpdateTimeout) {
clearTimeout(this._forceUpdateTimeout);
}
}
//
// Listeners
onSizeChange = ([minSize, maxSize]) => {
maxSize = maxSize === slider.max ? null : maxSize;
this.props.onSizeChange({ minSize, maxSize });
}
onMinSizeChange = ({ value }) => {
const minSize = getValue(value);
this.props.onSizeChange({
minSize,
maxSize: this.props.maxSize
});
}
onMaxSizeChange = ({ value }) => {
const maxSize = value === slider.max ? null : getValue(value);
this.props.onSizeChange({
minSize: this.props.minSize,
maxSize
});
}
//
// Render
render() {
const {
id,
quality,
title,
minSize,
maxSize,
advancedSettings,
onTitleChange
} = this.props;
const minBytes = minSize * 1024 * 1024;
const minThirty = formatBytes(minBytes * 30, 2);
const minSixty = formatBytes(minBytes * 60, 2);
const maxBytes = maxSize && maxSize * 1024 * 1024;
const maxThirty = maxBytes ? formatBytes(maxBytes * 30, 2) : 'Unlimited';
const maxSixty = maxBytes ? formatBytes(maxBytes * 60, 2) : 'Unlimited';
return (
<div className={styles.qualityDefinition}>
<div className={styles.quality}>
{quality.name}
</div>
<div className={styles.title}>
<TextInput
name={`${id}.${title}`}
value={title}
onChange={onTitleChange}
/>
</div>
<div className={styles.sizeLimit}>
<ReactSlider
min={slider.min}
max={slider.max}
step={slider.step}
minDistance={10}
value={[minSize || slider.min, maxSize || slider.max]}
withBars={true}
snapDragDisabled={true}
className={styles.slider}
barClassName={styles.bar}
handleClassName={styles.handle}
onChange={this.onSizeChange}
/>
<div className={styles.sizes}>
<div>
<Label kind={kinds.WARNING}>{minThirty}</Label>
<Label kind={kinds.INFO}>{minSixty}</Label>
</div>
<div>
<Label kind={kinds.WARNING}>{maxThirty}</Label>
<Label kind={kinds.INFO}>{maxSixty}</Label>
</div>
</div>
</div>
{
advancedSettings &&
<div className={styles.megabytesPerMinute}>
<div>
Min
<NumberInput
className={styles.sizeInput}
name={`${id}.min`}
value={minSize || slider.min}
min={slider.min}
max={maxSize ? maxSize - 10 : slider.max - 10}
isFloat={true}
onChange={this.onMinSizeChange}
/>
</div>
<div>
Max
<NumberInput
className={styles.sizeInput}
name={`${id}.min`}
value={maxSize || slider.max}
min={minSize + 10}
max={slider.max}
isFloat={true}
onChange={this.onMaxSizeChange}
/>
</div>
</div>
}
</div>
);
}
}
QualityDefinition.propTypes = {
id: PropTypes.number.isRequired,
quality: PropTypes.object.isRequired,
title: PropTypes.string.isRequired,
minSize: PropTypes.number,
maxSize: PropTypes.number,
advancedSettings: PropTypes.bool.isRequired,
onTitleChange: PropTypes.func.isRequired,
onSizeChange: PropTypes.func.isRequired
};
export default QualityDefinition;
@@ -0,0 +1,70 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { setQualityDefinitionValue } from 'Store/Actions/settingsActions';
import { clearPendingChanges } from 'Store/Actions/baseActions';
import QualityDefinition from './QualityDefinition';
function mapStateToProps(state) {
return {
advancedSettings: state.settings.advancedSettings
};
}
const mapDispatchToProps = {
setQualityDefinitionValue,
clearPendingChanges
};
class QualityDefinitionConnector extends Component {
componentWillUnmount() {
this.props.clearPendingChanges({ section: 'settings.qualityDefinitions' });
}
//
// Listeners
onTitleChange = ({ value }) => {
this.props.setQualityDefinitionValue({ id: this.props.id, name: 'title', value });
}
onSizeChange = ({ minSize, maxSize }) => {
const {
id,
minSize: currentMinSize,
maxSize: currentMaxSize
} = this.props;
if (minSize !== currentMinSize) {
this.props.setQualityDefinitionValue({ id, name: 'minSize', value: minSize });
}
if (minSize !== currentMaxSize) {
this.props.setQualityDefinitionValue({ id, name: 'maxSize', value: maxSize });
}
}
//
// Render
render() {
return (
<QualityDefinition
{...this.props}
onTitleChange={this.onTitleChange}
onSizeChange={this.onSizeChange}
/>
);
}
}
QualityDefinitionConnector.propTypes = {
id: PropTypes.number.isRequired,
minSize: PropTypes.number,
maxSize: PropTypes.number,
setQualityDefinitionValue: PropTypes.func.isRequired,
clearPendingChanges: PropTypes.func.isRequired
};
export default connect(mapStateToProps, mapDispatchToProps)(QualityDefinitionConnector);
@@ -0,0 +1,41 @@
.header {
display: flex;
font-weight: bold;
}
.quality,
.title {
flex: 0 1 250px;
}
.sizeLimit {
flex: 0 1 500px;
}
.megabytesPerMinute {
flex: 0 0 250px;
}
.sizeLimitHelpTextContainer {
display: flex;
justify-content: flex-end;
margin-top: 20px;
max-width: 1000px;
}
.sizeLimitHelpText {
max-width: 500px;
color: $helpTextColor;
}
@media only screen and (max-width: $breakpointSmall) {
.header {
display: none;
}
.definitions {
&:first-child {
border-top: none;
}
}
}
@@ -0,0 +1,63 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import FieldSet from 'Components/FieldSet';
import PageSectionContent from 'Components/Page/PageSectionContent';
import QualityDefinitionConnector from './QualityDefinitionConnector';
import styles from './QualityDefinitions.css';
class QualityDefinitions extends Component {
//
// Render
render() {
const {
items,
...otherProps
} = this.props;
return (
<FieldSet legend="Quality Definitions">
<PageSectionContent
errorMessage="Unable to load Quality Definitions"
{...otherProps}
>
<div className={styles.header}>
<div className={styles.quality}>Quality</div>
<div className={styles.title}>Title</div>
<div className={styles.sizeLimit}>Size Limit</div>
<div className={styles.megabytesPerMinute}>Megabytes Per Minute</div>
</div>
<div className={styles.definitions}>
{
items.map((item) => {
return (
<QualityDefinitionConnector
key={item.id}
{...item}
/>
);
})
}
</div>
<div className={styles.sizeLimitHelpTextContainer}>
<div className={styles.sizeLimitHelpText}>
Limits are automatically adjusted for the series runtime and number of episodes in the file.
</div>
</div>
</PageSectionContent>
</FieldSet>
);
}
}
QualityDefinitions.propTypes = {
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
defaultProfile: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired
};
export default QualityDefinitions;
@@ -0,0 +1,90 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchQualityDefinitions, saveQualityDefinitions } from 'Store/Actions/settingsActions';
import QualityDefinitions from './QualityDefinitions';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.qualityDefinitions,
(qualityDefinitions) => {
const items = qualityDefinitions.items.map((item) => {
const pendingChanges = qualityDefinitions.pendingChanges[item.id] || {};
return Object.assign({}, item, pendingChanges);
});
return {
...qualityDefinitions,
items,
hasPendingChanges: !_.isEmpty(qualityDefinitions.pendingChanges)
};
}
);
}
const mapDispatchToProps = {
dispatchFetchQualityDefinitions: fetchQualityDefinitions,
dispatchSaveQualityDefinitions: saveQualityDefinitions
};
class QualityDefinitionsConnector extends Component {
//
// Lifecycle
componentDidMount() {
this.props.dispatchFetchQualityDefinitions();
const {
dispatchFetchQualityDefinitions,
dispatchSaveQualityDefinitions,
onChildMounted
} = this.props;
dispatchFetchQualityDefinitions();
onChildMounted(dispatchSaveQualityDefinitions);
}
componentDidUpdate(prevProps) {
const {
hasPendingChanges,
isSaving,
onChildStateChange
} = this.props;
if (
prevProps.isSaving !== isSaving ||
prevProps.hasPendingChanges !== hasPendingChanges
) {
onChildStateChange({
isSaving,
hasPendingChanges
});
}
}
//
// Render
render() {
return (
<QualityDefinitions
{...this.props}
/>
);
}
}
QualityDefinitionsConnector.propTypes = {
isSaving: PropTypes.bool.isRequired,
hasPendingChanges: PropTypes.bool.isRequired,
dispatchFetchQualityDefinitions: PropTypes.func.isRequired,
dispatchSaveQualityDefinitions: PropTypes.func.isRequired,
onChildMounted: PropTypes.func.isRequired,
onChildStateChange: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps, null, { withRef: true })(QualityDefinitionsConnector);