mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2025-09-17 17:14:18 +02:00
Cleanup unused frontend components
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
.modal {
|
||||
composes: modalBody from '~Components/Modal/Modal.css';
|
||||
|
||||
height: 90%;
|
||||
}
|
@@ -1,27 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import AddIndexerModalContentConnector from './AddIndexerModalContentConnector';
|
||||
import styles from './AddIndexerModal.css';
|
||||
|
||||
function AddIndexerModal({ isOpen, onModalClose, ...otherProps }) {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
className={styles.modal}
|
||||
>
|
||||
<AddIndexerModalContentConnector
|
||||
{...otherProps}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
AddIndexerModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default AddIndexerModal;
|
@@ -1,30 +0,0 @@
|
||||
.indexers {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.modalBody {
|
||||
composes: modalBody from '~Components/Modal/ModalBody.css';
|
||||
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.filterInput {
|
||||
composes: input from '~Components/Form/TextInput.css';
|
||||
|
||||
flex: 0 0 auto;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.alert {
|
||||
composes: alert from '~Components/Alert.css';
|
||||
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.scroller {
|
||||
flex: 1 1 auto;
|
||||
}
|
@@ -1,174 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import TextInput from 'Components/Form/TextInput';
|
||||
import Button from 'Components/Link/Button';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import Scroller from 'Components/Scroller/Scroller';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import { kinds, scrollDirections } from 'Helpers/Props';
|
||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import SelectIndexerRow from './SelectIndexerRow';
|
||||
import styles from './AddIndexerModalContent.css';
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: 'protocol',
|
||||
label: 'Protocol',
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'privacy',
|
||||
label: 'Privacy',
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
}
|
||||
];
|
||||
|
||||
class AddIndexerModalContent extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
filter: ''
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onFilterChange = ({ value }) => {
|
||||
this.setState({ filter: value });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
indexers,
|
||||
onIndexerSelect,
|
||||
sortKey,
|
||||
sortDirection,
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
onSortPress,
|
||||
onModalClose
|
||||
} = this.props;
|
||||
|
||||
const filter = this.state.filter;
|
||||
const filterLower = filter.toLowerCase();
|
||||
|
||||
const errorMessage = getErrorMessage(error, 'Unable to load indexers');
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
Add Indexer
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody
|
||||
className={styles.modalBody}
|
||||
scrollDirection={scrollDirections.NONE}
|
||||
>
|
||||
<TextInput
|
||||
className={styles.filterInput}
|
||||
placeholder={translate('FilterPlaceHolder')}
|
||||
name="filter"
|
||||
value={filter}
|
||||
autoFocus={true}
|
||||
onChange={this.onFilterChange}
|
||||
/>
|
||||
|
||||
<Alert
|
||||
kind={kinds.INFO}
|
||||
className={styles.alert}
|
||||
>
|
||||
<div>
|
||||
{translate('ProwlarrSupportsAnyIndexer')}
|
||||
</div>
|
||||
</Alert>
|
||||
|
||||
<Scroller
|
||||
className={styles.scroller}
|
||||
autoFocus={false}
|
||||
>
|
||||
{
|
||||
isFetching ? <LoadingIndicator /> : null
|
||||
}
|
||||
{
|
||||
error ? <div>{errorMessage}</div> : null
|
||||
}
|
||||
{
|
||||
isPopulated && !!indexers.length ?
|
||||
<Table
|
||||
columns={columns}
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onSortPress={onSortPress}
|
||||
>
|
||||
<TableBody>
|
||||
{
|
||||
indexers.map((indexer) => {
|
||||
return indexer.name.toLowerCase().includes(filterLower) ?
|
||||
(
|
||||
<SelectIndexerRow
|
||||
key={indexer.name}
|
||||
implementation={indexer.implementation}
|
||||
{...indexer}
|
||||
onIndexerSelect={onIndexerSelect}
|
||||
/>
|
||||
) :
|
||||
null;
|
||||
})
|
||||
}
|
||||
</TableBody>
|
||||
</Table> :
|
||||
null
|
||||
}
|
||||
</Scroller>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button
|
||||
onPress={onModalClose}
|
||||
>
|
||||
{translate('Close')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AddIndexerModalContent.propTypes = {
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
sortKey: PropTypes.string,
|
||||
sortDirection: PropTypes.string,
|
||||
onSortPress: PropTypes.func.isRequired,
|
||||
indexers: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onIndexerSelect: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default AddIndexerModalContent;
|
@@ -1,82 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { fetchIndexerSchema, selectIndexerSchema, setIndexerSchemaSort } from 'Store/Actions/indexerActions';
|
||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import AddIndexerModalContent from './AddIndexerModalContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createClientSideCollectionSelector('indexers.schema'),
|
||||
(indexers) => {
|
||||
const {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
items,
|
||||
sortDirection,
|
||||
sortKey
|
||||
} = indexers;
|
||||
|
||||
return {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
indexers: items,
|
||||
sortKey,
|
||||
sortDirection
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchIndexerSchema,
|
||||
selectIndexerSchema,
|
||||
setIndexerSchemaSort
|
||||
};
|
||||
|
||||
class AddIndexerModalContentConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchIndexerSchema();
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onIndexerSelect = ({ implementation, name }) => {
|
||||
this.props.selectIndexerSchema({ implementation, name });
|
||||
this.props.onModalClose({ indexerSelected: true });
|
||||
}
|
||||
|
||||
onSortPress = (sortKey, sortDirection) => {
|
||||
this.props.setIndexerSchemaSort({ sortKey, sortDirection });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<AddIndexerModalContent
|
||||
{...this.props}
|
||||
onSortPress={this.onSortPress}
|
||||
onIndexerSelect={this.onIndexerSelect}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AddIndexerModalContentConnector.propTypes = {
|
||||
fetchIndexerSchema: PropTypes.func.isRequired,
|
||||
selectIndexerSchema: PropTypes.func.isRequired,
|
||||
setIndexerSchemaSort: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(AddIndexerModalContentConnector);
|
@@ -1,49 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import MenuItem from 'Components/Menu/MenuItem';
|
||||
|
||||
class AddIndexerPresetMenuItem extends Component {
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onPress = () => {
|
||||
const {
|
||||
name,
|
||||
implementation
|
||||
} = this.props;
|
||||
|
||||
this.props.onPress({
|
||||
name,
|
||||
implementation
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
name,
|
||||
implementation,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
{...otherProps}
|
||||
onPress={this.onPress}
|
||||
>
|
||||
{name}
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AddIndexerPresetMenuItem.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
implementation: PropTypes.string.isRequired,
|
||||
onPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default AddIndexerPresetMenuItem;
|
@@ -1,27 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import EditIndexerModalContentConnector from './EditIndexerModalContentConnector';
|
||||
|
||||
function EditIndexerModal({ isOpen, onModalClose, ...otherProps }) {
|
||||
return (
|
||||
<Modal
|
||||
size={sizes.MEDIUM}
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<EditIndexerModalContentConnector
|
||||
{...otherProps}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
EditIndexerModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default EditIndexerModal;
|
@@ -1,65 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||
import { cancelSaveIndexer, cancelTestIndexer } from 'Store/Actions/indexerActions';
|
||||
import EditIndexerModal from './EditIndexerModal';
|
||||
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
const section = 'indexers';
|
||||
|
||||
return {
|
||||
dispatchClearPendingChanges() {
|
||||
dispatch(clearPendingChanges({ section }));
|
||||
},
|
||||
|
||||
dispatchCancelTestIndexer() {
|
||||
dispatch(cancelTestIndexer({ section }));
|
||||
},
|
||||
|
||||
dispatchCancelSaveIndexer() {
|
||||
dispatch(cancelSaveIndexer({ section }));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class EditIndexerModalConnector extends Component {
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onModalClose = () => {
|
||||
this.props.dispatchClearPendingChanges();
|
||||
this.props.dispatchCancelTestIndexer();
|
||||
this.props.dispatchCancelSaveIndexer();
|
||||
this.props.onModalClose();
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
dispatchClearPendingChanges,
|
||||
dispatchCancelTestIndexer,
|
||||
dispatchCancelSaveIndexer,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<EditIndexerModal
|
||||
{...otherProps}
|
||||
onModalClose={this.onModalClose}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
EditIndexerModalConnector.propTypes = {
|
||||
onModalClose: PropTypes.func.isRequired,
|
||||
dispatchClearPendingChanges: PropTypes.func.isRequired,
|
||||
dispatchCancelTestIndexer: PropTypes.func.isRequired,
|
||||
dispatchCancelSaveIndexer: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(null, createMapDispatchToProps)(EditIndexerModalConnector);
|
@@ -1,5 +0,0 @@
|
||||
.deleteButton {
|
||||
composes: button from '~Components/Link/Button.css';
|
||||
|
||||
margin-right: auto;
|
||||
}
|
@@ -1,195 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import ProviderFieldFormGroup from 'Components/Form/ProviderFieldFormGroup';
|
||||
import Button from 'Components/Link/Button';
|
||||
import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './EditIndexerModalContent.css';
|
||||
|
||||
function EditIndexerModalContent(props) {
|
||||
const {
|
||||
advancedSettings,
|
||||
isFetching,
|
||||
error,
|
||||
isSaving,
|
||||
isTesting,
|
||||
saveError,
|
||||
item,
|
||||
onInputChange,
|
||||
onFieldChange,
|
||||
onModalClose,
|
||||
onSavePress,
|
||||
onTestPress,
|
||||
onDeleteIndexerPress,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
const {
|
||||
id,
|
||||
implementationName,
|
||||
name,
|
||||
enable,
|
||||
redirect,
|
||||
supportsRss,
|
||||
supportsRedirect,
|
||||
fields,
|
||||
priority
|
||||
} = item;
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{`${id ? translate('EditIndexer') : translate('AddIndexer')} - ${implementationName}`}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
{
|
||||
isFetching &&
|
||||
<LoadingIndicator />
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>
|
||||
{translate('UnableToAddANewIndexerPleaseTryAgain')}
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && !error &&
|
||||
<Form {...otherProps}>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Name')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TEXT}
|
||||
name="name"
|
||||
{...name}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Enable')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="enable"
|
||||
helpTextWarning={supportsRss.value ? undefined : translate('RSSIsNotSupportedWithThisIndexer')}
|
||||
isDisabled={!supportsRss.value}
|
||||
{...enable}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Redirect')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="redirect"
|
||||
helpText={'Redirect incoming download requests for indexer instead of Proxying using Prowlarr'}
|
||||
isDisabled={!supportsRedirect.value}
|
||||
{...redirect}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{
|
||||
fields.map((field) => {
|
||||
return (
|
||||
<ProviderFieldFormGroup
|
||||
key={field.name}
|
||||
advancedSettings={advancedSettings}
|
||||
provider="indexer"
|
||||
providerData={item}
|
||||
{...field}
|
||||
onChange={onFieldChange}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
>
|
||||
<FormLabel>{translate('IndexerPriority')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.NUMBER}
|
||||
name="priority"
|
||||
helpText={translate('IndexerPriorityHelpText')}
|
||||
min={1}
|
||||
max={50}
|
||||
{...priority}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
{
|
||||
id &&
|
||||
<Button
|
||||
className={styles.deleteButton}
|
||||
kind={kinds.DANGER}
|
||||
onPress={onDeleteIndexerPress}
|
||||
>
|
||||
{translate('Delete')}
|
||||
</Button>
|
||||
}
|
||||
|
||||
<SpinnerErrorButton
|
||||
isSpinning={isTesting}
|
||||
error={saveError}
|
||||
onPress={onTestPress}
|
||||
>
|
||||
{translate('Test')}
|
||||
</SpinnerErrorButton>
|
||||
|
||||
<Button
|
||||
onPress={onModalClose}
|
||||
>
|
||||
{translate('Cancel')}
|
||||
</Button>
|
||||
|
||||
<SpinnerErrorButton
|
||||
isSpinning={isSaving}
|
||||
error={saveError}
|
||||
onPress={onSavePress}
|
||||
>
|
||||
{translate('Save')}
|
||||
</SpinnerErrorButton>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
|
||||
EditIndexerModalContent.propTypes = {
|
||||
advancedSettings: PropTypes.bool.isRequired,
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
isTesting: PropTypes.bool.isRequired,
|
||||
saveError: PropTypes.object,
|
||||
item: PropTypes.object.isRequired,
|
||||
onInputChange: PropTypes.func.isRequired,
|
||||
onFieldChange: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired,
|
||||
onSavePress: PropTypes.func.isRequired,
|
||||
onTestPress: PropTypes.func.isRequired,
|
||||
onDeleteIndexerPress: PropTypes.func
|
||||
};
|
||||
|
||||
export default EditIndexerModalContent;
|
@@ -1,88 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { saveIndexer, setIndexerFieldValue, setIndexerValue, testIndexer } from 'Store/Actions/indexerActions';
|
||||
import createIndexerSchemaSelector from 'Store/Selectors/createIndexerSchemaSelector';
|
||||
import EditIndexerModalContent from './EditIndexerModalContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.settings.advancedSettings,
|
||||
createIndexerSchemaSelector(),
|
||||
(advancedSettings, indexer) => {
|
||||
return {
|
||||
advancedSettings,
|
||||
...indexer
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
setIndexerValue,
|
||||
setIndexerFieldValue,
|
||||
saveIndexer,
|
||||
testIndexer
|
||||
};
|
||||
|
||||
class EditIndexerModalContentConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevProps.isSaving && !this.props.isSaving && !this.props.saveError) {
|
||||
this.props.onModalClose();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onInputChange = ({ name, value }) => {
|
||||
this.props.setIndexerValue({ name, value });
|
||||
}
|
||||
|
||||
onFieldChange = ({ name, value }) => {
|
||||
this.props.setIndexerFieldValue({ name, value });
|
||||
}
|
||||
|
||||
onSavePress = () => {
|
||||
this.props.saveIndexer({ id: this.props.id });
|
||||
}
|
||||
|
||||
onTestPress = () => {
|
||||
this.props.testIndexer({ id: this.props.id });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<EditIndexerModalContent
|
||||
{...this.props}
|
||||
onSavePress={this.onSavePress}
|
||||
onTestPress={this.onTestPress}
|
||||
onInputChange={this.onInputChange}
|
||||
onFieldChange={this.onFieldChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
EditIndexerModalContentConnector.propTypes = {
|
||||
id: PropTypes.number,
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
saveError: PropTypes.object,
|
||||
item: PropTypes.object.isRequired,
|
||||
setIndexerValue: PropTypes.func.isRequired,
|
||||
setIndexerFieldValue: PropTypes.func.isRequired,
|
||||
saveIndexer: PropTypes.func.isRequired,
|
||||
testIndexer: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(EditIndexerModalContentConnector);
|
@@ -1,30 +0,0 @@
|
||||
.indexer {
|
||||
composes: card from '~Components/Card.css';
|
||||
|
||||
width: 290px;
|
||||
}
|
||||
|
||||
.nameContainer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.name {
|
||||
@add-mixin truncate;
|
||||
|
||||
margin-bottom: 20px;
|
||||
font-weight: 300;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.cloneButton {
|
||||
composes: button from '~Components/Link/IconButton.css';
|
||||
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.enabled {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 5px;
|
||||
}
|
@@ -1,152 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Card from 'Components/Card';
|
||||
import Label from 'Components/Label';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import EditIndexerModalConnector from './EditIndexerModalConnector';
|
||||
import styles from './Indexer.css';
|
||||
|
||||
class Indexer extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isEditIndexerModalOpen: false,
|
||||
isDeleteIndexerModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onEditIndexerPress = () => {
|
||||
this.setState({ isEditIndexerModalOpen: true });
|
||||
}
|
||||
|
||||
onEditIndexerModalClose = () => {
|
||||
this.setState({ isEditIndexerModalOpen: false });
|
||||
}
|
||||
|
||||
onDeleteIndexerPress = () => {
|
||||
this.setState({
|
||||
isEditIndexerModalOpen: false,
|
||||
isDeleteIndexerModalOpen: true
|
||||
});
|
||||
}
|
||||
|
||||
onDeleteIndexerModalClose= () => {
|
||||
this.setState({ isDeleteIndexerModalOpen: false });
|
||||
}
|
||||
|
||||
onConfirmDeleteIndexer = () => {
|
||||
this.props.onConfirmDeleteIndexer(this.props.id);
|
||||
}
|
||||
|
||||
onCloneIndexerPress = () => {
|
||||
const {
|
||||
id,
|
||||
onCloneIndexerPress
|
||||
} = this.props;
|
||||
|
||||
onCloneIndexerPress(id);
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
enable,
|
||||
supportsRss,
|
||||
priority,
|
||||
showPriority
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Card
|
||||
className={styles.indexer}
|
||||
overlayContent={true}
|
||||
onPress={this.onEditIndexerPress}
|
||||
>
|
||||
<div className={styles.nameContainer}>
|
||||
<div className={styles.name}>
|
||||
{name}
|
||||
</div>
|
||||
|
||||
<IconButton
|
||||
className={styles.cloneButton}
|
||||
title={translate('CloneIndexer')}
|
||||
name={icons.CLONE}
|
||||
onPress={this.onCloneIndexerPress}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.enabled}>
|
||||
|
||||
{
|
||||
supportsRss && enable &&
|
||||
<Label kind={kinds.SUCCESS}>
|
||||
RSS
|
||||
</Label>
|
||||
}
|
||||
|
||||
{
|
||||
showPriority &&
|
||||
<Label kind={kinds.DEFAULT}>
|
||||
{translate('Priority')}: {priority}
|
||||
</Label>
|
||||
}
|
||||
{
|
||||
!enable &&
|
||||
<Label
|
||||
kind={kinds.DISABLED}
|
||||
outline={true}
|
||||
>
|
||||
{translate('Disabled')}
|
||||
</Label>
|
||||
}
|
||||
</div>
|
||||
|
||||
<EditIndexerModalConnector
|
||||
id={id}
|
||||
isOpen={this.state.isEditIndexerModalOpen}
|
||||
onModalClose={this.onEditIndexerModalClose}
|
||||
onDeleteIndexerPress={this.onDeleteIndexerPress}
|
||||
/>
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={this.state.isDeleteIndexerModalOpen}
|
||||
kind={kinds.DANGER}
|
||||
title={translate('DeleteIndexer')}
|
||||
message={translate('DeleteIndexerMessageText', [name])}
|
||||
confirmLabel={translate('Delete')}
|
||||
onConfirm={this.onConfirmDeleteIndexer}
|
||||
onCancel={this.onDeleteIndexerModalClose}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Indexer.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
enable: PropTypes.bool.isRequired,
|
||||
supportsRss: PropTypes.bool.isRequired,
|
||||
supportsSearch: PropTypes.bool.isRequired,
|
||||
onCloneIndexerPress: PropTypes.func.isRequired,
|
||||
onConfirmDeleteIndexer: PropTypes.func.isRequired,
|
||||
priority: PropTypes.number.isRequired,
|
||||
showPriority: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
export default Indexer;
|
@@ -1,20 +0,0 @@
|
||||
.indexers {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.addIndexer {
|
||||
composes: indexer from '~./Indexer.css';
|
||||
|
||||
background-color: $cardAlternateBackgroundColor;
|
||||
color: $gray;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.center {
|
||||
display: inline-block;
|
||||
padding: 5px 20px 0;
|
||||
border: 1px solid $borderColor;
|
||||
border-radius: 4px;
|
||||
background-color: $white;
|
||||
}
|
@@ -1,126 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Card from 'Components/Card';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Icon from 'Components/Icon';
|
||||
import PageSectionContent from 'Components/Page/PageSectionContent';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import AddIndexerModal from './AddIndexerModal';
|
||||
import EditIndexerModalConnector from './EditIndexerModalConnector';
|
||||
import Indexer from './Indexer';
|
||||
import styles from './Indexers.css';
|
||||
|
||||
class Indexers extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isAddIndexerModalOpen: false,
|
||||
isEditIndexerModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onAddIndexerPress = () => {
|
||||
this.setState({ isAddIndexerModalOpen: true });
|
||||
}
|
||||
|
||||
onCloneIndexerPress = (id) => {
|
||||
this.props.dispatchCloneIndexer({ id });
|
||||
this.setState({ isEditIndexerModalOpen: true });
|
||||
}
|
||||
|
||||
onAddIndexerModalClose = ({ indexerSelected = false } = {}) => {
|
||||
this.setState({
|
||||
isAddIndexerModalOpen: false,
|
||||
isEditIndexerModalOpen: indexerSelected
|
||||
});
|
||||
}
|
||||
|
||||
onEditIndexerModalClose = () => {
|
||||
this.setState({ isEditIndexerModalOpen: false });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
items,
|
||||
dispatchCloneIndexer,
|
||||
onConfirmDeleteIndexer,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
isAddIndexerModalOpen,
|
||||
isEditIndexerModalOpen
|
||||
} = this.state;
|
||||
|
||||
const showPriority = items.some((index) => index.priority !== 25);
|
||||
|
||||
return (
|
||||
<FieldSet legend={translate('Indexers')}>
|
||||
<PageSectionContent
|
||||
errorMessage={translate('UnableToLoadIndexers')}
|
||||
{...otherProps}
|
||||
>
|
||||
<div className={styles.indexers}>
|
||||
{
|
||||
items.map((item) => {
|
||||
return (
|
||||
<Indexer
|
||||
key={item.id}
|
||||
{...item}
|
||||
showPriority={showPriority}
|
||||
onCloneIndexerPress={this.onCloneIndexerPress}
|
||||
onConfirmDeleteIndexer={onConfirmDeleteIndexer}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
<Card
|
||||
className={styles.addIndexer}
|
||||
onPress={this.onAddIndexerPress}
|
||||
>
|
||||
<div className={styles.center}>
|
||||
<Icon
|
||||
name={icons.ADD}
|
||||
size={45}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<AddIndexerModal
|
||||
isOpen={isAddIndexerModalOpen}
|
||||
onModalClose={this.onAddIndexerModalClose}
|
||||
/>
|
||||
|
||||
<EditIndexerModalConnector
|
||||
isOpen={isEditIndexerModalOpen}
|
||||
onModalClose={this.onEditIndexerModalClose}
|
||||
/>
|
||||
</PageSectionContent>
|
||||
</FieldSet>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Indexers.propTypes = {
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
dispatchCloneIndexer: PropTypes.func.isRequired,
|
||||
onConfirmDeleteIndexer: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default Indexers;
|
@@ -1,58 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { cloneIndexer, deleteIndexer, fetchIndexers } from 'Store/Actions/indexerActions';
|
||||
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
||||
import sortByName from 'Utilities/Array/sortByName';
|
||||
import Indexers from './Indexers';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createSortedSectionSelector('indexers', sortByName),
|
||||
(indexers) => indexers
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
dispatchFetchIndexers: fetchIndexers,
|
||||
dispatchDeleteIndexer: deleteIndexer,
|
||||
dispatchCloneIndexer: cloneIndexer
|
||||
};
|
||||
|
||||
class IndexersConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
this.props.dispatchFetchIndexers();
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onConfirmDeleteIndexer = (id) => {
|
||||
this.props.dispatchDeleteIndexer({ id });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Indexers
|
||||
{...this.props}
|
||||
onConfirmDeleteIndexer={this.onConfirmDeleteIndexer}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
IndexersConnector.propTypes = {
|
||||
dispatchFetchIndexers: PropTypes.func.isRequired,
|
||||
dispatchDeleteIndexer: PropTypes.func.isRequired,
|
||||
dispatchCloneIndexer: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(IndexersConnector);
|
@@ -1,5 +0,0 @@
|
||||
.protocol {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 32px;
|
||||
}
|
@@ -1,60 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import TableRowButton from 'Components/Table/TableRowButton';
|
||||
import ProtocolLabel from 'Indexer/Index/Table/ProtocolLabel';
|
||||
import styles from './SelectIndexerRow.css';
|
||||
|
||||
class SelectIndexerRow extends Component {
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onPress = () => {
|
||||
const {
|
||||
implementation,
|
||||
name
|
||||
} = this.props;
|
||||
|
||||
this.props.onIndexerSelect({ implementation, name });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
protocol,
|
||||
privacy,
|
||||
name
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<TableRowButton onPress={this.onPress}>
|
||||
<TableRowCell className={styles.protocol}>
|
||||
<ProtocolLabel
|
||||
protocol={protocol}
|
||||
/>
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell>
|
||||
{name}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell>
|
||||
{privacy}
|
||||
</TableRowCell>
|
||||
</TableRowButton>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectIndexerRow.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
protocol: PropTypes.string.isRequired,
|
||||
privacy: PropTypes.string.isRequired,
|
||||
implementation: PropTypes.string.isRequired,
|
||||
onIndexerSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default SelectIndexerRow;
|
@@ -1,93 +0,0 @@
|
||||
.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(3n+1) {
|
||||
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 400px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
@@ -1,308 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import ReactSlider from 'react-slider';
|
||||
import NumberInput from 'Components/Form/NumberInput';
|
||||
import TextInput from 'Components/Form/TextInput';
|
||||
import Label from 'Components/Label';
|
||||
import Popover from 'Components/Tooltip/Popover';
|
||||
import { kinds, tooltipPositions } from 'Helpers/Props';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
import roundNumber from 'Utilities/Number/roundNumber';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import QualityDefinitionLimits from './QualityDefinitionLimits';
|
||||
import styles from './QualityDefinition.css';
|
||||
|
||||
const MIN = 0;
|
||||
const MAX = 400;
|
||||
|
||||
const slider = {
|
||||
min: MIN,
|
||||
max: roundNumber(Math.pow(MAX, 1 / 1.1)),
|
||||
step: 0.1
|
||||
};
|
||||
|
||||
function getValue(inputValue) {
|
||||
if (inputValue < MIN) {
|
||||
return MIN;
|
||||
}
|
||||
|
||||
if (inputValue > MAX) {
|
||||
return MAX;
|
||||
}
|
||||
|
||||
return roundNumber(inputValue);
|
||||
}
|
||||
|
||||
function getSliderValue(value, defaultValue) {
|
||||
const sliderValue = value ? Math.pow(value, 1 / 1.1) : defaultValue;
|
||||
|
||||
return roundNumber(sliderValue);
|
||||
}
|
||||
|
||||
class QualityDefinition extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
sliderMinSize: getSliderValue(props.minSize, slider.min),
|
||||
sliderMaxSize: getSliderValue(props.maxSize, slider.max),
|
||||
sliderPreferredSize: getSliderValue(props.preferredSize, (slider.max - 3))
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onSliderChange = ([sliderMinSize, sliderPreferredSize, sliderMaxSize]) => {
|
||||
this.setState({
|
||||
sliderMinSize,
|
||||
sliderMaxSize,
|
||||
sliderPreferredSize
|
||||
});
|
||||
|
||||
this.props.onSizeChange({
|
||||
minSize: roundNumber(Math.pow(sliderMinSize, 1.1)),
|
||||
preferredSize: sliderPreferredSize === (slider.max - 3) ? null : roundNumber(Math.pow(sliderPreferredSize, 1.1)),
|
||||
maxSize: sliderMaxSize === slider.max ? null : roundNumber(Math.pow(sliderMaxSize, 1.1))
|
||||
});
|
||||
}
|
||||
|
||||
onAfterSliderChange = () => {
|
||||
const {
|
||||
minSize,
|
||||
maxSize,
|
||||
preferredSize
|
||||
} = this.props;
|
||||
|
||||
this.setState({
|
||||
sliderMiSize: getSliderValue(minSize, slider.min),
|
||||
sliderMaxSize: getSliderValue(maxSize, slider.max),
|
||||
sliderPreferredSize: getSliderValue(preferredSize, (slider.max - 3)) // fix
|
||||
});
|
||||
}
|
||||
|
||||
onMinSizeChange = ({ value }) => {
|
||||
const minSize = getValue(value);
|
||||
|
||||
this.setState({
|
||||
sliderMinSize: getSliderValue(minSize, slider.min)
|
||||
});
|
||||
|
||||
this.props.onSizeChange({
|
||||
minSize,
|
||||
maxSize: this.props.maxSize,
|
||||
preferredSize: this.props.preferredSize
|
||||
});
|
||||
}
|
||||
|
||||
onPreferredSizeChange = ({ value }) => {
|
||||
const preferredSize = value === (MAX - 3) ? null : getValue(value);
|
||||
|
||||
this.setState({
|
||||
sliderPreferredSize: getSliderValue(preferredSize, slider.preferred)
|
||||
});
|
||||
|
||||
this.props.onSizeChange({
|
||||
minSize: this.props.minSize,
|
||||
maxSize: this.props.maxSize,
|
||||
preferredSize
|
||||
});
|
||||
}
|
||||
|
||||
onMaxSizeChange = ({ value }) => {
|
||||
const maxSize = value === MAX ? null : getValue(value);
|
||||
|
||||
this.setState({
|
||||
sliderMaxSize: getSliderValue(maxSize, slider.max)
|
||||
});
|
||||
|
||||
this.props.onSizeChange({
|
||||
minSize: this.props.minSize,
|
||||
maxSize,
|
||||
preferredSize: this.props.preferredSize
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
id,
|
||||
quality,
|
||||
title,
|
||||
minSize,
|
||||
maxSize,
|
||||
preferredSize,
|
||||
advancedSettings,
|
||||
onTitleChange
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
sliderMinSize,
|
||||
sliderMaxSize,
|
||||
sliderPreferredSize
|
||||
} = this.state;
|
||||
|
||||
const minBytes = minSize * 1024 * 1024;
|
||||
const minSixty = `${formatBytes(minBytes * 60)}/h`;
|
||||
|
||||
const preferredBytes = preferredSize * 1024 * 1024;
|
||||
const preferredSixty = preferredBytes ? `${formatBytes(preferredBytes * 60)}/h` : 'Unlimited';
|
||||
|
||||
const maxBytes = maxSize && maxSize * 1024 * 1024;
|
||||
const maxSixty = maxBytes ? `${formatBytes(maxBytes * 60)}/h` : '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={3}
|
||||
value={[sliderMinSize, sliderPreferredSize, sliderMaxSize]}
|
||||
withTracks={true}
|
||||
allowCross={false}
|
||||
snapDragDisabled={true}
|
||||
className={styles.slider}
|
||||
trackClassName={styles.bar}
|
||||
thumbClassName={styles.handle}
|
||||
onChange={this.onSliderChange}
|
||||
onAfterChange={this.onAfterSliderChange}
|
||||
/>
|
||||
|
||||
<div className={styles.sizes}>
|
||||
<div>
|
||||
<Popover
|
||||
anchor={
|
||||
<Label kind={kinds.INFO}>{minSixty}</Label>
|
||||
}
|
||||
title={translate('MinimumLimits')}
|
||||
body={
|
||||
<QualityDefinitionLimits
|
||||
bytes={minBytes}
|
||||
message={translate('NoMinimumForAnyRuntime')}
|
||||
/>
|
||||
}
|
||||
position={tooltipPositions.BOTTOM}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Popover
|
||||
anchor={
|
||||
<Label kind={kinds.SUCCESS}>{preferredSixty}</Label>
|
||||
}
|
||||
title={translate('PreferredSize')}
|
||||
body={
|
||||
<QualityDefinitionLimits
|
||||
bytes={preferredBytes}
|
||||
message={translate('NoLimitForAnyRuntime')}
|
||||
/>
|
||||
}
|
||||
position={tooltipPositions.BOTTOM}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Popover
|
||||
anchor={
|
||||
<Label kind={kinds.WARNING}>{maxSixty}</Label>
|
||||
}
|
||||
title={translate('MaximumLimits')}
|
||||
body={
|
||||
<QualityDefinitionLimits
|
||||
bytes={maxBytes}
|
||||
message={translate('NoLimitForAnyRuntime')}
|
||||
/>
|
||||
}
|
||||
position={tooltipPositions.BOTTOM}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{
|
||||
advancedSettings &&
|
||||
<div className={styles.megabytesPerMinute}>
|
||||
<div>
|
||||
Min
|
||||
|
||||
<NumberInput
|
||||
className={styles.sizeInput}
|
||||
name={`${id}.min`}
|
||||
value={minSize || MIN}
|
||||
min={MIN}
|
||||
max={preferredSize ? preferredSize - 5 : MAX - 5}
|
||||
step={0.1}
|
||||
isFloat={true}
|
||||
onChange={this.onMinSizeChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Preferred
|
||||
|
||||
<NumberInput
|
||||
className={styles.sizeInput}
|
||||
name={`${id}.min`}
|
||||
value={preferredSize || MAX - 5}
|
||||
min={MIN}
|
||||
max={maxSize ? maxSize - 5 : MAX - 5}
|
||||
step={0.1}
|
||||
isFloat={true}
|
||||
onChange={this.onPreferredSizeChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Max
|
||||
|
||||
<NumberInput
|
||||
className={styles.sizeInput}
|
||||
name={`${id}.min`}
|
||||
value={maxSize || MAX}
|
||||
min={minSize + 5}
|
||||
max={MAX}
|
||||
step={0.1}
|
||||
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,
|
||||
preferredSize: PropTypes.number,
|
||||
advancedSettings: PropTypes.bool.isRequired,
|
||||
onTitleChange: PropTypes.func.isRequired,
|
||||
onSizeChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default QualityDefinition;
|
@@ -1,70 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||
import { setQualityDefinitionValue } from 'Store/Actions/settingsActions';
|
||||
import QualityDefinition from './QualityDefinition';
|
||||
|
||||
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, preferredSize }) => {
|
||||
const {
|
||||
id,
|
||||
minSize: currentMinSize,
|
||||
maxSize: currentMaxSize,
|
||||
preferredSize: currentPreferredSize
|
||||
} = this.props;
|
||||
|
||||
if (minSize !== currentMinSize) {
|
||||
this.props.setQualityDefinitionValue({ id, name: 'minSize', value: minSize });
|
||||
}
|
||||
|
||||
if (maxSize !== currentMaxSize) {
|
||||
this.props.setQualityDefinitionValue({ id, name: 'maxSize', value: maxSize });
|
||||
}
|
||||
|
||||
if (preferredSize !== currentPreferredSize) {
|
||||
this.props.setQualityDefinitionValue({ id, name: 'preferredSize', value: preferredSize });
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<QualityDefinition
|
||||
{...this.props}
|
||||
onTitleChange={this.onTitleChange}
|
||||
onSizeChange={this.onSizeChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
QualityDefinitionConnector.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
minSize: PropTypes.number,
|
||||
maxSize: PropTypes.number,
|
||||
preferredSize: PropTypes.number,
|
||||
setQualityDefinitionValue: PropTypes.func.isRequired,
|
||||
clearPendingChanges: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(null, mapDispatchToProps)(QualityDefinitionConnector);
|
@@ -1,40 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
function QualityDefinitionLimits(props) {
|
||||
const {
|
||||
bytes,
|
||||
message
|
||||
} = props;
|
||||
|
||||
if (!bytes) {
|
||||
return <div>{message}</div>;
|
||||
}
|
||||
|
||||
const sixty = formatBytes(bytes * 60);
|
||||
const ninety = formatBytes(bytes * 90);
|
||||
const hundredTwenty = formatBytes(bytes * 120);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
{translate('MinutesSixty', [sixty])}
|
||||
</div>
|
||||
<div>
|
||||
{translate('MinutesNinety', [ninety])}
|
||||
</div>
|
||||
<div>
|
||||
{translate('MinutesHundredTwenty', [hundredTwenty])}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
QualityDefinitionLimits.propTypes = {
|
||||
bytes: PropTypes.number,
|
||||
message: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default QualityDefinitionLimits;
|
@@ -1,41 +0,0 @@
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,74 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import PageSectionContent from 'Components/Page/PageSectionContent';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import QualityDefinitionConnector from './QualityDefinitionConnector';
|
||||
import styles from './QualityDefinitions.css';
|
||||
|
||||
class QualityDefinitions extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
items,
|
||||
advancedSettings,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<FieldSet legend={translate('QualityDefinitions')}>
|
||||
<PageSectionContent
|
||||
errorMessage={translate('UnableToLoadQualityDefinitions')}
|
||||
{...otherProps}
|
||||
>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.quality}>Quality</div>
|
||||
<div className={styles.title}>Title</div>
|
||||
<div className={styles.sizeLimit}>Size Limit</div>
|
||||
|
||||
{
|
||||
advancedSettings ?
|
||||
<div className={styles.megabytesPerMinute}>
|
||||
Megabytes Per Minute
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className={styles.definitions}>
|
||||
{
|
||||
items.map((item) => {
|
||||
return (
|
||||
<QualityDefinitionConnector
|
||||
key={item.id}
|
||||
{...item}
|
||||
advancedSettings={advancedSettings}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className={styles.sizeLimitHelpTextContainer}>
|
||||
<div className={styles.sizeLimitHelpText}>
|
||||
Limits are automatically adjusted for the movie runtime.
|
||||
</div>
|
||||
</div>
|
||||
</PageSectionContent>
|
||||
</FieldSet>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
QualityDefinitions.propTypes = {
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
defaultProfile: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
advancedSettings: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
export default QualityDefinitions;
|
@@ -1,92 +0,0 @@
|
||||
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,
|
||||
(state) => state.settings.advancedSettings,
|
||||
(qualityDefinitions, advancedSettings) => {
|
||||
const items = qualityDefinitions.items.map((item) => {
|
||||
const pendingChanges = qualityDefinitions.pendingChanges[item.id] || {};
|
||||
|
||||
return Object.assign({}, item, pendingChanges);
|
||||
});
|
||||
|
||||
return {
|
||||
...qualityDefinitions,
|
||||
items,
|
||||
hasPendingChanges: !_.isEmpty(qualityDefinitions.pendingChanges),
|
||||
advancedSettings
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
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)(QualityDefinitionsConnector);
|
@@ -1,69 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import QualityDefinitionsConnector from './Definition/QualityDefinitionsConnector';
|
||||
|
||||
class Quality extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this._saveCallback = null;
|
||||
|
||||
this.state = {
|
||||
isSaving: false,
|
||||
hasPendingChanges: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onChildMounted = (saveCallback) => {
|
||||
this._saveCallback = saveCallback;
|
||||
}
|
||||
|
||||
onChildStateChange = (payload) => {
|
||||
this.setState(payload);
|
||||
}
|
||||
|
||||
onSavePress = () => {
|
||||
if (this._saveCallback) {
|
||||
this._saveCallback();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
isSaving,
|
||||
hasPendingChanges
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<PageContent title={translate('QualitySettings')}>
|
||||
<SettingsToolbarConnector
|
||||
isSaving={isSaving}
|
||||
hasPendingChanges={hasPendingChanges}
|
||||
onSavePress={this.onSavePress}
|
||||
/>
|
||||
|
||||
<PageContentBody>
|
||||
<QualityDefinitionsConnector
|
||||
onChildMounted={this.onChildMounted}
|
||||
onChildStateChange={this.onChildStateChange}
|
||||
/>
|
||||
</PageContentBody>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Quality;
|
Reference in New Issue
Block a user