1
0
mirror of https://github.com/Radarr/Radarr.git synced 2024-09-17 15:02:34 +02:00

New: Import and export custom formats

This commit is contained in:
ta264 2020-09-07 21:26:25 +01:00
parent e014826b17
commit c3a89c46f5
13 changed files with 673 additions and 8 deletions

View File

@ -55,6 +55,7 @@ import {
faEye as fasEye, faEye as fasEye,
faFastBackward as fasFastBackward, faFastBackward as fasFastBackward,
faFastForward as fasFastForward, faFastForward as fasFastForward,
faFileExport as fasFileExport,
faFileInvoice as farFileInvoice, faFileInvoice as farFileInvoice,
faFilm as fasFilm, faFilm as fasFilm,
faFilter as fasFilter, faFilter as fasFilter,
@ -145,6 +146,7 @@ export const EDIT = fasWrench;
export const MOVIE_FILE = farFileVideo; export const MOVIE_FILE = farFileVideo;
export const EXPAND = fasChevronCircleDown; export const EXPAND = fasChevronCircleDown;
export const EXPAND_INDETERMINATE = fasChevronCircleRight; export const EXPAND_INDETERMINATE = fasChevronCircleRight;
export const EXPORT = fasFileExport;
export const EXTERNAL_LINK = fasExternalLinkAlt; export const EXTERNAL_LINK = fasExternalLinkAlt;
export const FATAL = fasTimesCircle; export const FATAL = fasTimesCircle;
export const FILE = farFile; export const FILE = farFile;

View File

@ -7,6 +7,7 @@ import ConfirmModal from 'Components/Modal/ConfirmModal';
import { icons, kinds } from 'Helpers/Props'; import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import EditCustomFormatModalConnector from './EditCustomFormatModalConnector'; import EditCustomFormatModalConnector from './EditCustomFormatModalConnector';
import ExportCustomFormatModal from './ExportCustomFormatModal';
import styles from './CustomFormat.css'; import styles from './CustomFormat.css';
class CustomFormat extends Component { class CustomFormat extends Component {
@ -19,6 +20,7 @@ class CustomFormat extends Component {
this.state = { this.state = {
isEditCustomFormatModalOpen: false, isEditCustomFormatModalOpen: false,
isExportCustomFormatModalOpen: false,
isDeleteCustomFormatModalOpen: false isDeleteCustomFormatModalOpen: false
}; };
} }
@ -34,6 +36,14 @@ class CustomFormat extends Component {
this.setState({ isEditCustomFormatModalOpen: false }); this.setState({ isEditCustomFormatModalOpen: false });
} }
onExportCustomFormatPress = () => {
this.setState({ isExportCustomFormatModalOpen: true });
}
onExportCustomFormatModalClose = () => {
this.setState({ isExportCustomFormatModalOpen: false });
}
onDeleteCustomFormatPress = () => { onDeleteCustomFormatPress = () => {
this.setState({ this.setState({
isEditCustomFormatModalOpen: false, isEditCustomFormatModalOpen: false,
@ -80,12 +90,21 @@ class CustomFormat extends Component {
{name} {name}
</div> </div>
<IconButton <div>
className={styles.cloneButton} <IconButton
title={translate('CloneProfile')} className={styles.cloneButton}
name={icons.CLONE} title={translate('CloneProfile')}
onPress={this.onCloneCustomFormatPress} name={icons.CLONE}
/> onPress={this.onCloneCustomFormatPress}
/>
<IconButton
className={styles.cloneButton}
title={translate('CloneProfile')}
name={icons.EXPORT}
onPress={this.onExportCustomFormatPress}
/>
</div>
</div> </div>
<div> <div>
@ -122,6 +141,12 @@ class CustomFormat extends Component {
onDeleteCustomFormatPress={this.onDeleteCustomFormatPress} onDeleteCustomFormatPress={this.onDeleteCustomFormatPress}
/> />
<ExportCustomFormatModal
id={id}
isOpen={this.state.isExportCustomFormatModalOpen}
onModalClose={this.onExportCustomFormatModalClose}
/>
<ConfirmModal <ConfirmModal
isOpen={this.state.isDeleteCustomFormatModalOpen} isOpen={this.state.isDeleteCustomFormatModalOpen}
kind={kinds.DANGER} kind={kinds.DANGER}

View File

@ -16,6 +16,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader'; import ModalHeader from 'Components/Modal/ModalHeader';
import { icons, inputTypes, kinds } from 'Helpers/Props'; import { icons, inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import ImportCustomFormatModal from './ImportCustomFormatModal';
import AddSpecificationModal from './Specifications/AddSpecificationModal'; import AddSpecificationModal from './Specifications/AddSpecificationModal';
import EditSpecificationModalConnector from './Specifications/EditSpecificationModalConnector'; import EditSpecificationModalConnector from './Specifications/EditSpecificationModalConnector';
import Specification from './Specifications/Specification'; import Specification from './Specifications/Specification';
@ -31,7 +32,8 @@ class EditCustomFormatModalContent extends Component {
this.state = { this.state = {
isAddSpecificationModalOpen: false, isAddSpecificationModalOpen: false,
isEditSpecificationModalOpen: false isEditSpecificationModalOpen: false,
isImportCustomFormatModalOpen: false
}; };
} }
@ -53,6 +55,14 @@ class EditCustomFormatModalContent extends Component {
this.setState({ isEditSpecificationModalOpen: false }); this.setState({ isEditSpecificationModalOpen: false });
} }
onImportPress = () => {
this.setState({ isImportCustomFormatModalOpen: true });
}
onImportCustomFormatModalClose = () => {
this.setState({ isImportCustomFormatModalOpen: false });
}
// //
// Render // Render
@ -76,7 +86,8 @@ class EditCustomFormatModalContent extends Component {
const { const {
isAddSpecificationModalOpen, isAddSpecificationModalOpen,
isEditSpecificationModalOpen isEditSpecificationModalOpen,
isImportCustomFormatModalOpen
} = this.state; } = this.state;
const { const {
@ -176,6 +187,12 @@ class EditCustomFormatModalContent extends Component {
isOpen={isEditSpecificationModalOpen} isOpen={isEditSpecificationModalOpen}
onModalClose={this.onEditSpecificationModalClose} onModalClose={this.onEditSpecificationModalClose}
/> />
<ImportCustomFormatModal
isOpen={isImportCustomFormatModalOpen}
onModalClose={this.onImportCustomFormatModalClose}
/>
</div> </div>
} }
</div> </div>
@ -192,6 +209,16 @@ class EditCustomFormatModalContent extends Component {
</Button> </Button>
} }
{
!id &&
<Button
className={styles.deleteButton}
onPress={this.onImportPress}
>
{translate('Import')}
</Button>
}
<Button <Button
onPress={onModalClose} onPress={onModalClose}
> >

View File

@ -0,0 +1,61 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props';
import ExportCustomFormatModalContentConnector from './ExportCustomFormatModalContentConnector';
class ExportCustomFormatModal extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
height: 'auto'
};
}
//
// Listeners
onContentHeightChange = (height) => {
if (this.state.height === 'auto' || height > this.state.height) {
this.setState({ height });
}
}
//
// Render
render() {
const {
isOpen,
onModalClose,
...otherProps
} = this.props;
return (
<Modal
style={{ height: `${this.state.height}px` }}
isOpen={isOpen}
size={sizes.LARGE}
onModalClose={onModalClose}
>
<ExportCustomFormatModalContentConnector
{...otherProps}
onContentHeightChange={this.onContentHeightChange}
onModalClose={onModalClose}
/>
</Modal>
);
}
}
ExportCustomFormatModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default ExportCustomFormatModal;

View File

@ -0,0 +1,5 @@
.button {
composes: button from '~Components/Link/Button.css';
position: relative;
}

View File

@ -0,0 +1,84 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Button from 'Components/Link/Button';
import ClipboardButton from 'Components/Link/ClipboardButton';
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 { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './ExportCustomFormatModalContent.css';
class ExportCustomFormatModalContent extends Component {
//
// Render
render() {
const {
isFetching,
error,
json,
specificationsPopulated,
onModalClose
} = this.props;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{translate('ExportCustomFormat')}
</ModalHeader>
<ModalBody>
<div>
{
isFetching &&
<LoadingIndicator />
}
{
!isFetching && !!error &&
<div>
{translate('UnableToLoadCustomFormats')}
</div>
}
{
!isFetching && !error && specificationsPopulated &&
<div>
<pre>
{json}
</pre>
</div>
}
</div>
</ModalBody>
<ModalFooter>
<ClipboardButton
className={styles.button}
value={json}
kind={kinds.DEFAULT}
/>
<Button
onPress={onModalClose}
>
{translate('Close')}
</Button>
</ModalFooter>
</ModalContent>
);
}
}
ExportCustomFormatModalContent.propTypes = {
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
json: PropTypes.string.isRequired,
specificationsPopulated: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default ExportCustomFormatModalContent;

View File

@ -0,0 +1,83 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchCustomFormatSpecifications } from 'Store/Actions/settingsActions';
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
import ExportCustomFormatModalContent from './ExportCustomFormatModalContent';
const blacklistedProperties = ['id', 'implementationName', 'infoLink'];
function replacer(key, value) {
if (blacklistedProperties.includes(key)) {
return undefined;
}
// provider fields
if (key === 'fields') {
return value.reduce((acc, cur) => {
acc[cur.name] = cur.value;
return acc;
}, {});
}
// regular setting values
if (value.hasOwnProperty('value')) {
return value.value;
}
return value;
}
function createMapStateToProps() {
return createSelector(
(state) => state.settings.advancedSettings,
createProviderSettingsSelector('customFormats'),
(state) => state.settings.customFormatSpecifications,
(advancedSettings, customFormat, specifications) => {
const json = customFormat.item ? JSON.stringify(customFormat.item, replacer, 2) : '';
return {
advancedSettings,
...customFormat,
json,
specificationsPopulated: specifications.isPopulated,
specifications: specifications.items
};
}
);
}
const mapDispatchToProps = {
fetchCustomFormatSpecifications
};
class ExportCustomFormatModalContentConnector extends Component {
//
// Lifecycle
componentDidMount() {
const {
id
} = this.props;
this.props.fetchCustomFormatSpecifications({ id });
}
//
// Render
render() {
return (
<ExportCustomFormatModalContent
{...this.props}
/>
);
}
}
ExportCustomFormatModalContentConnector.propTypes = {
id: PropTypes.number,
fetchCustomFormatSpecifications: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(ExportCustomFormatModalContentConnector);

View File

@ -0,0 +1,61 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props';
import ImportCustomFormatModalContentConnector from './ImportCustomFormatModalContentConnector';
class ImportCustomFormatModal extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
height: 'auto'
};
}
//
// Listeners
onContentHeightChange = (height) => {
if (this.state.height === 'auto' || height > this.state.height) {
this.setState({ height });
}
}
//
// Render
render() {
const {
isOpen,
onModalClose,
...otherProps
} = this.props;
return (
<Modal
style={{ height: `${this.state.height}px` }}
isOpen={isOpen}
size={sizes.LARGE}
onModalClose={onModalClose}
>
<ImportCustomFormatModalContentConnector
{...otherProps}
onContentHeightChange={this.onContentHeightChange}
onModalClose={onModalClose}
/>
</Modal>
);
}
}
ImportCustomFormatModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default ImportCustomFormatModal;

View File

@ -0,0 +1,5 @@
.input {
composes: input from '~Components/Form/TextArea.css';
font-family: $monoSpaceFontFamily;
}

View File

@ -0,0 +1,152 @@
import PropTypes from 'prop-types';
import React, { Component } 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 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, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './ImportCustomFormatModalContent.css';
class ImportCustomFormatModalContent extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this._importTimeout = null;
this.state = {
json: '',
isSpinning: false,
parseError: null
};
}
componentWillUnmount() {
if (this._importTimeout) {
clearTimeout(this._importTimeout);
}
}
//
// Control
onChange = (event) => {
this.setState({ json: event.value });
}
onImportPress = () => {
this.setState({ isSpinning: true });
// this is a bodge as we need to register a isSpinning: true to get the spinner button to update
this._importTimeout = setTimeout(this.doImport, 250);
}
doImport = () => {
const parseError = this.props.onImportPress(this.state.json);
this.setState({
parseError,
isSpinning: false
});
if (!parseError) {
this.props.onModalClose();
}
}
//
// Render
render() {
const {
isFetching,
error,
specificationsPopulated,
onModalClose
} = this.props;
const {
json,
isSpinning,
parseError
} = this.state;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{translate('ImportCustomFormat')}
</ModalHeader>
<ModalBody>
<div>
{
isFetching &&
<LoadingIndicator />
}
{
!isFetching && !!error &&
<div>
{translate('UnableToLoadCustomFormats')}
</div>
}
{
!isFetching && !error && specificationsPopulated &&
<Form>
<FormGroup size={sizes.MEDIUM}>
<FormLabel>
{translate('CustomFormatJSON')}
</FormLabel>
<FormInputGroup
key={0}
inputClassName={styles.input}
type={inputTypes.TEXT_AREA}
name="customFormatJson"
value={json}
onChange={this.onChange}
placeholder={'{\n "name": "Custom Format"\n}'}
errors={parseError ? [parseError] : []}
/>
</FormGroup>
</Form>
}
</div>
</ModalBody>
<ModalFooter>
<Button
onPress={onModalClose}
>
{translate('Cancel')}
</Button>
<SpinnerErrorButton
onPress={this.onImportPress}
isSpinning={isSpinning}
error={parseError}
>
{translate('Import')}
</SpinnerErrorButton>
</ModalFooter>
</ModalContent>
);
}
}
ImportCustomFormatModalContent.propTypes = {
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
specificationsPopulated: PropTypes.bool.isRequired,
onImportPress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default ImportCustomFormatModalContent;

View File

@ -0,0 +1,146 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { clearPendingChanges } from 'Store/Actions/baseActions';
import { clearCustomFormatSpecificationPending, deleteAllCustomFormatSpecification, fetchCustomFormatSpecificationSchema, saveCustomFormatSpecification, selectCustomFormatSpecificationSchema, setCustomFormatSpecificationFieldValue, setCustomFormatSpecificationValue, setCustomFormatValue } from 'Store/Actions/settingsActions';
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
import translate from 'Utilities/String/translate';
import ImportCustomFormatModalContent from './ImportCustomFormatModalContent';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.advancedSettings,
createProviderSettingsSelector('customFormats'),
(state) => state.settings.customFormatSpecifications,
(advancedSettings, customFormat, specifications) => {
return {
advancedSettings,
...customFormat,
specificationsPopulated: specifications.isPopulated,
specificationSchema: specifications.schema
};
}
);
}
const mapDispatchToProps = {
deleteAllCustomFormatSpecification,
clearCustomFormatSpecificationPending,
clearPendingChanges,
saveCustomFormatSpecification,
selectCustomFormatSpecificationSchema,
setCustomFormatSpecificationFieldValue,
setCustomFormatSpecificationValue,
setCustomFormatValue,
fetchCustomFormatSpecificationSchema
};
class ImportCustomFormatModalContentConnector extends Component {
//
// Lifecycle
componentDidMount() {
this.props.fetchCustomFormatSpecificationSchema();
}
//
// Listeners
clearPending = () => {
this.props.clearPendingChanges({ section: 'settings.customFormats' });
this.props.clearCustomFormatSpecificationPending();
this.props.deleteAllCustomFormatSpecification();
}
onImportPress = (payload) => {
this.clearPending();
try {
const cf = JSON.parse(payload);
this.parseCf(cf);
} catch (err) {
this.clearPending();
return {
message: err.message,
detailedMessage: err.stack
};
}
return null;
}
parseCf = (cf) => {
for (const [key, value] of Object.entries(cf)) {
if (key === 'specifications') {
for (const spec of value) {
this.parseSpecification(spec);
}
} else if (key !== 'id') {
this.props.setCustomFormatValue({ name: key, value });
}
}
}
parseSpecification = (spec) => {
const selectedImplementation = _.find(this.props.specificationSchema, { implementation: spec.implementation });
if (!selectedImplementation) {
throw new Error(translate('CustomFormatUnknownCondition', [spec.implementation]));
}
this.props.selectCustomFormatSpecificationSchema({ implementation: spec.implementation });
for (const [key, value] of Object.entries(spec)) {
if (key === 'fields') {
this.parseFields(value, selectedImplementation);
} else if (key !== 'id') {
this.props.setCustomFormatSpecificationValue({ name: key, value });
}
}
this.props.saveCustomFormatSpecification();
}
parseFields = (fields, schema) => {
for (const [key, value] of Object.entries(fields)) {
const field = _.find(schema.fields, { name: key });
if (!field) {
throw new Error(translate('CustomFormatUnknownConditionOption', [key, schema.implementationName]));
}
this.props.setCustomFormatSpecificationFieldValue({ name: key, value });
}
}
//
// Render
render() {
return (
<ImportCustomFormatModalContent
{...this.props}
onImportPress={this.onImportPress}
/>
);
}
}
ImportCustomFormatModalContentConnector.propTypes = {
specificationSchema: PropTypes.arrayOf(PropTypes.object).isRequired,
clearPendingChanges: PropTypes.func.isRequired,
deleteAllCustomFormatSpecification: PropTypes.func.isRequired,
clearCustomFormatSpecificationPending: PropTypes.func.isRequired,
saveCustomFormatSpecification: PropTypes.func.isRequired,
fetchCustomFormatSpecificationSchema: PropTypes.func.isRequired,
selectCustomFormatSpecificationSchema: PropTypes.func.isRequired,
setCustomFormatSpecificationValue: PropTypes.func.isRequired,
setCustomFormatSpecificationFieldValue: PropTypes.func.isRequired,
setCustomFormatValue: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(ImportCustomFormatModalContentConnector);

View File

@ -27,6 +27,7 @@ export const SET_CUSTOM_FORMAT_SPECIFICATION_VALUE = 'settings/customFormatSpeci
export const SET_CUSTOM_FORMAT_SPECIFICATION_FIELD_VALUE = 'settings/customFormatSpecifications/setCustomFormatSpecificationFieldValue'; export const SET_CUSTOM_FORMAT_SPECIFICATION_FIELD_VALUE = 'settings/customFormatSpecifications/setCustomFormatSpecificationFieldValue';
export const SAVE_CUSTOM_FORMAT_SPECIFICATION = 'settings/customFormatSpecifications/saveCustomFormatSpecification'; export const SAVE_CUSTOM_FORMAT_SPECIFICATION = 'settings/customFormatSpecifications/saveCustomFormatSpecification';
export const DELETE_CUSTOM_FORMAT_SPECIFICATION = 'settings/customFormatSpecifications/deleteCustomFormatSpecification'; export const DELETE_CUSTOM_FORMAT_SPECIFICATION = 'settings/customFormatSpecifications/deleteCustomFormatSpecification';
export const DELETE_ALL_CUSTOM_FORMAT_SPECIFICATION = 'settings/customFormatSpecifications/deleteCustomFormatSpecification';
export const CLONE_CUSTOM_FORMAT_SPECIFICATION = 'settings/customFormatSpecifications/cloneCustomFormatSpecification'; export const CLONE_CUSTOM_FORMAT_SPECIFICATION = 'settings/customFormatSpecifications/cloneCustomFormatSpecification';
export const CLEAR_CUSTOM_FORMAT_SPECIFICATIONS = 'settings/customFormatSpecifications/clearCustomFormatSpecifications'; export const CLEAR_CUSTOM_FORMAT_SPECIFICATIONS = 'settings/customFormatSpecifications/clearCustomFormatSpecifications';
export const CLEAR_CUSTOM_FORMAT_SPECIFICATION_PENDING = 'settings/customFormatSpecifications/clearCustomFormatSpecificationPending'; export const CLEAR_CUSTOM_FORMAT_SPECIFICATION_PENDING = 'settings/customFormatSpecifications/clearCustomFormatSpecificationPending';
@ -39,6 +40,7 @@ export const selectCustomFormatSpecificationSchema = createAction(SELECT_CUSTOM_
export const saveCustomFormatSpecification = createThunk(SAVE_CUSTOM_FORMAT_SPECIFICATION); export const saveCustomFormatSpecification = createThunk(SAVE_CUSTOM_FORMAT_SPECIFICATION);
export const deleteCustomFormatSpecification = createThunk(DELETE_CUSTOM_FORMAT_SPECIFICATION); export const deleteCustomFormatSpecification = createThunk(DELETE_CUSTOM_FORMAT_SPECIFICATION);
export const deleteAllCustomFormatSpecification = createThunk(DELETE_ALL_CUSTOM_FORMAT_SPECIFICATION);
export const setCustomFormatSpecificationValue = createAction(SET_CUSTOM_FORMAT_SPECIFICATION_VALUE, (payload) => { export const setCustomFormatSpecificationValue = createAction(SET_CUSTOM_FORMAT_SPECIFICATION_VALUE, (payload) => {
return { return {
@ -137,6 +139,13 @@ export default {
return dispatch(removeItem({ section, id })); return dispatch(removeItem({ section, id }));
}, },
[DELETE_ALL_CUSTOM_FORMAT_SPECIFICATION]: (getState, payload, dispatch) => {
return dispatch(set({
section,
items: []
}));
},
[CLEAR_CUSTOM_FORMAT_SPECIFICATION_PENDING]: (getState, payload, dispatch) => { [CLEAR_CUSTOM_FORMAT_SPECIFICATION_PENDING]: (getState, payload, dispatch) => {
return dispatch(set({ return dispatch(set({
section, section,

View File

@ -115,6 +115,9 @@
"CreateGroup": "Create group", "CreateGroup": "Create group",
"Crew": "Crew", "Crew": "Crew",
"CustomFilters": "Custom Filters", "CustomFilters": "Custom Filters",
"CustomFormatJSON": "Custom Format JSON",
"CustomFormatUnknownCondition": "Unknown Custom Format condition '{0}'",
"CustomFormatUnknownConditionOption": "Unknown option '{0}' for condition '{1}'",
"CustomFormats": "Custom Formats", "CustomFormats": "Custom Formats",
"CustomFormatScore": "Custom Format score", "CustomFormatScore": "Custom Format score",
"CustomFormatsSettings": "Custom Formats Settings", "CustomFormatsSettings": "Custom Formats Settings",
@ -219,6 +222,7 @@
"ExcludeMovie": "Exclude Movie", "ExcludeMovie": "Exclude Movie",
"ExistingMovies": "Existing Movie(s)", "ExistingMovies": "Existing Movie(s)",
"ExistingTag": "Existing tag", "ExistingTag": "Existing tag",
"ExportCustomFormat": "Export Custom Format",
"Extension": "Extension", "Extension": "Extension",
"ExtraFileExtensionsHelpTexts1": "Comma separated list of extra files to import (.nfo will be imported as .nfo-orig)", "ExtraFileExtensionsHelpTexts1": "Comma separated list of extra files to import (.nfo will be imported as .nfo-orig)",
"ExtraFileExtensionsHelpTexts2": "Examples: '.sub, .nfo' or 'sub,nfo'", "ExtraFileExtensionsHelpTexts2": "Examples: '.sub, .nfo' or 'sub,nfo'",
@ -281,6 +285,7 @@
"IgnoredPlaceHolder": "Add new restriction", "IgnoredPlaceHolder": "Add new restriction",
"IllRestartLater": "I'll restart later", "IllRestartLater": "I'll restart later",
"Import": "Import", "Import": "Import",
"ImportCustomFormat": "Import Custom Format",
"Imported": "Imported", "Imported": "Imported",
"ImportedTo": "Imported To", "ImportedTo": "Imported To",
"ImportExistingMovies": "Import Existing Movies", "ImportExistingMovies": "Import Existing Movies",