parent
e361f18837
commit
2f62494adc
@ -1,26 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import Modal from 'Components/Modal/Modal';
|
|
||||||
import EditSeriesModalContentConnector from './EditSeriesModalContentConnector';
|
|
||||||
|
|
||||||
function EditSeriesModal({ isOpen, onModalClose, ...otherProps }) {
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
isOpen={isOpen}
|
|
||||||
onModalClose={onModalClose}
|
|
||||||
>
|
|
||||||
<EditSeriesModalContentConnector
|
|
||||||
{...otherProps}
|
|
||||||
onModalClose={onModalClose}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
EditSeriesModal.propTypes = {
|
|
||||||
...EditSeriesModalContentConnector.propTypes,
|
|
||||||
isOpen: PropTypes.bool.isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EditSeriesModal;
|
|
@ -0,0 +1,34 @@
|
|||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import Modal from 'Components/Modal/Modal';
|
||||||
|
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||||
|
import { EditSeriesModalContentProps } from './EditSeriesModalContent';
|
||||||
|
import EditSeriesModalContentConnector from './EditSeriesModalContentConnector';
|
||||||
|
|
||||||
|
interface EditSeriesModalProps extends EditSeriesModalContentProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function EditSeriesModal({
|
||||||
|
isOpen,
|
||||||
|
onModalClose,
|
||||||
|
...otherProps
|
||||||
|
}: EditSeriesModalProps) {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const handleModalClose = useCallback(() => {
|
||||||
|
dispatch(clearPendingChanges({ section: 'series' }));
|
||||||
|
onModalClose();
|
||||||
|
}, [dispatch, onModalClose]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={isOpen} onModalClose={handleModalClose}>
|
||||||
|
<EditSeriesModalContentConnector
|
||||||
|
{...otherProps}
|
||||||
|
onModalClose={handleModalClose}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditSeriesModal;
|
@ -1,40 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
|
||||||
import EditSeriesModal from './EditSeriesModal';
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
|
||||||
clearPendingChanges
|
|
||||||
};
|
|
||||||
|
|
||||||
class EditSeriesModalConnector extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onModalClose = () => {
|
|
||||||
this.props.clearPendingChanges({ section: 'series' });
|
|
||||||
this.props.onModalClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<EditSeriesModal
|
|
||||||
{...this.props}
|
|
||||||
onModalClose={this.onModalClose}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
EditSeriesModalConnector.propTypes = {
|
|
||||||
...EditSeriesModal.propTypes,
|
|
||||||
onModalClose: PropTypes.func.isRequired,
|
|
||||||
clearPendingChanges: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(undefined, mapDispatchToProps)(EditSeriesModalConnector);
|
|
@ -1,240 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import SeriesMonitorNewItemsOptionsPopoverContent from 'AddSeries/SeriesMonitorNewItemsOptionsPopoverContent';
|
|
||||||
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 Icon from 'Components/Icon';
|
|
||||||
import Button from 'Components/Link/Button';
|
|
||||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
|
||||||
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 Popover from 'Components/Tooltip/Popover';
|
|
||||||
import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props';
|
|
||||||
import MoveSeriesModal from 'Series/MoveSeries/MoveSeriesModal';
|
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
import styles from './EditSeriesModalContent.css';
|
|
||||||
|
|
||||||
class EditSeriesModalContent extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
isConfirmMoveModalOpen: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onCancelPress = () => {
|
|
||||||
this.setState({ isConfirmMoveModalOpen: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
onSavePress = () => {
|
|
||||||
const {
|
|
||||||
isPathChanging,
|
|
||||||
onSavePress
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
if (isPathChanging && !this.state.isConfirmMoveModalOpen) {
|
|
||||||
this.setState({ isConfirmMoveModalOpen: true });
|
|
||||||
} else {
|
|
||||||
this.setState({ isConfirmMoveModalOpen: false });
|
|
||||||
|
|
||||||
onSavePress(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onMoveSeriesPress = () => {
|
|
||||||
this.setState({ isConfirmMoveModalOpen: false });
|
|
||||||
|
|
||||||
this.props.onSavePress(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
title,
|
|
||||||
item,
|
|
||||||
isSaving,
|
|
||||||
originalPath,
|
|
||||||
onInputChange,
|
|
||||||
onModalClose,
|
|
||||||
onDeleteSeriesPress,
|
|
||||||
...otherProps
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const {
|
|
||||||
monitored,
|
|
||||||
monitorNewItems,
|
|
||||||
seasonFolder,
|
|
||||||
qualityProfileId,
|
|
||||||
seriesType,
|
|
||||||
path,
|
|
||||||
tags
|
|
||||||
} = item;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ModalContent onModalClose={onModalClose}>
|
|
||||||
<ModalHeader>
|
|
||||||
{translate('EditSeriesModalHeader', { title })}
|
|
||||||
</ModalHeader>
|
|
||||||
|
|
||||||
<ModalBody>
|
|
||||||
<Form {...otherProps}>
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>{translate('Monitored')}</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.CHECK}
|
|
||||||
name="monitored"
|
|
||||||
helpText={translate('MonitoredEpisodesHelpText')}
|
|
||||||
{...monitored}
|
|
||||||
onChange={onInputChange}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>
|
|
||||||
{translate('MonitorNewSeasons')}
|
|
||||||
<Popover
|
|
||||||
anchor={
|
|
||||||
<Icon
|
|
||||||
className={styles.labelIcon}
|
|
||||||
name={icons.INFO}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
title={translate('MonitorNewSeasons')}
|
|
||||||
body={<SeriesMonitorNewItemsOptionsPopoverContent />}
|
|
||||||
position={tooltipPositions.RIGHT}
|
|
||||||
/>
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.MONITOR_NEW_ITEMS_SELECT}
|
|
||||||
name="monitorNewItems"
|
|
||||||
helpText={translate('MonitorNewSeasonsHelpText')}
|
|
||||||
{...monitorNewItems}
|
|
||||||
onChange={onInputChange}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>{translate('UseSeasonFolder')}</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.CHECK}
|
|
||||||
name="seasonFolder"
|
|
||||||
helpText={translate('UseSeasonFolderHelpText')}
|
|
||||||
{...seasonFolder}
|
|
||||||
onChange={onInputChange}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>{translate('QualityProfile')}</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.QUALITY_PROFILE_SELECT}
|
|
||||||
name="qualityProfileId"
|
|
||||||
{...qualityProfileId}
|
|
||||||
onChange={onInputChange}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>{translate('SeriesType')}</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.SERIES_TYPE_SELECT}
|
|
||||||
name="seriesType"
|
|
||||||
{...seriesType}
|
|
||||||
helpText={translate('SeriesTypesHelpText')}
|
|
||||||
onChange={onInputChange}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>{translate('Path')}</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.PATH}
|
|
||||||
name="path"
|
|
||||||
{...path}
|
|
||||||
onChange={onInputChange}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>{translate('Tags')}</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.TAG}
|
|
||||||
name="tags"
|
|
||||||
{...tags}
|
|
||||||
onChange={onInputChange}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
</Form>
|
|
||||||
</ModalBody>
|
|
||||||
|
|
||||||
<ModalFooter>
|
|
||||||
<Button
|
|
||||||
className={styles.deleteButton}
|
|
||||||
kind={kinds.DANGER}
|
|
||||||
onPress={onDeleteSeriesPress}
|
|
||||||
>
|
|
||||||
{translate('Delete')}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onPress={onModalClose}
|
|
||||||
>
|
|
||||||
{translate('Cancel')}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<SpinnerButton
|
|
||||||
isSpinning={isSaving}
|
|
||||||
onPress={this.onSavePress}
|
|
||||||
>
|
|
||||||
{translate('Save')}
|
|
||||||
</SpinnerButton>
|
|
||||||
</ModalFooter>
|
|
||||||
|
|
||||||
<MoveSeriesModal
|
|
||||||
originalPath={originalPath}
|
|
||||||
destinationPath={path.value}
|
|
||||||
isOpen={this.state.isConfirmMoveModalOpen}
|
|
||||||
onModalClose={this.onCancelPress}
|
|
||||||
onSavePress={this.onSavePress}
|
|
||||||
onMoveSeriesPress={this.onMoveSeriesPress}
|
|
||||||
/>
|
|
||||||
</ModalContent>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
EditSeriesModalContent.propTypes = {
|
|
||||||
seriesId: PropTypes.number.isRequired,
|
|
||||||
title: PropTypes.string.isRequired,
|
|
||||||
item: PropTypes.object.isRequired,
|
|
||||||
isSaving: PropTypes.bool.isRequired,
|
|
||||||
isPathChanging: PropTypes.bool.isRequired,
|
|
||||||
originalPath: PropTypes.string.isRequired,
|
|
||||||
onInputChange: PropTypes.func.isRequired,
|
|
||||||
onSavePress: PropTypes.func.isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired,
|
|
||||||
onDeleteSeriesPress: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EditSeriesModalContent;
|
|
@ -0,0 +1,248 @@
|
|||||||
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import SeriesMonitorNewItemsOptionsPopoverContent from 'AddSeries/SeriesMonitorNewItemsOptionsPopoverContent';
|
||||||
|
import AppState from 'App/State/AppState';
|
||||||
|
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 Icon from 'Components/Icon';
|
||||||
|
import Button from 'Components/Link/Button';
|
||||||
|
import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
|
||||||
|
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 Popover from 'Components/Tooltip/Popover';
|
||||||
|
import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props';
|
||||||
|
import MoveSeriesModal from 'Series/MoveSeries/MoveSeriesModal';
|
||||||
|
import useSeries from 'Series/useSeries';
|
||||||
|
import { saveSeries, setSeriesValue } from 'Store/Actions/seriesActions';
|
||||||
|
import selectSettings from 'Store/Selectors/selectSettings';
|
||||||
|
import { InputChanged } from 'typings/inputs';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import styles from './EditSeriesModalContent.css';
|
||||||
|
|
||||||
|
export interface EditSeriesModalContentProps {
|
||||||
|
seriesId: number;
|
||||||
|
onModalClose: () => void;
|
||||||
|
onDeleteSeriesPress: () => void;
|
||||||
|
}
|
||||||
|
function EditSeriesModalContent({
|
||||||
|
seriesId,
|
||||||
|
onModalClose,
|
||||||
|
onDeleteSeriesPress,
|
||||||
|
}: EditSeriesModalContentProps) {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const {
|
||||||
|
title,
|
||||||
|
monitored,
|
||||||
|
monitorNewItems,
|
||||||
|
seasonFolder,
|
||||||
|
qualityProfileId,
|
||||||
|
seriesType,
|
||||||
|
path,
|
||||||
|
tags,
|
||||||
|
} = useSeries(seriesId)!;
|
||||||
|
const { isSaving, saveError, pendingChanges } = useSelector(
|
||||||
|
(state: AppState) => state.series
|
||||||
|
);
|
||||||
|
|
||||||
|
const isPathChanging = pendingChanges.path && path !== pendingChanges.path;
|
||||||
|
|
||||||
|
const [isConfirmMoveModalOpen, setIsConfirmMoveModalOpen] = useState(false);
|
||||||
|
|
||||||
|
const { settings, ...otherSettings } = useMemo(() => {
|
||||||
|
return selectSettings(
|
||||||
|
{
|
||||||
|
monitored,
|
||||||
|
monitorNewItems,
|
||||||
|
seasonFolder,
|
||||||
|
qualityProfileId,
|
||||||
|
seriesType,
|
||||||
|
path,
|
||||||
|
tags,
|
||||||
|
},
|
||||||
|
pendingChanges,
|
||||||
|
saveError
|
||||||
|
);
|
||||||
|
}, [
|
||||||
|
monitored,
|
||||||
|
monitorNewItems,
|
||||||
|
seasonFolder,
|
||||||
|
qualityProfileId,
|
||||||
|
seriesType,
|
||||||
|
path,
|
||||||
|
tags,
|
||||||
|
pendingChanges,
|
||||||
|
saveError,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const handleInputChange = useCallback(
|
||||||
|
({ name, value }: InputChanged) => {
|
||||||
|
// @ts-expect-error actions aren't typed
|
||||||
|
dispatch(setSeriesValue({ name, value }));
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleCancelPress = useCallback(() => {
|
||||||
|
setIsConfirmMoveModalOpen(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSavePress = useCallback(() => {
|
||||||
|
if (isPathChanging && !isConfirmMoveModalOpen) {
|
||||||
|
setIsConfirmMoveModalOpen(true);
|
||||||
|
} else {
|
||||||
|
setIsConfirmMoveModalOpen(false);
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
saveSeries({
|
||||||
|
id: seriesId,
|
||||||
|
moveFiles: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [seriesId, isPathChanging, isConfirmMoveModalOpen, dispatch]);
|
||||||
|
|
||||||
|
const handleMoveSeriesPress = useCallback(() => {
|
||||||
|
setIsConfirmMoveModalOpen(false);
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
saveSeries({
|
||||||
|
id: seriesId,
|
||||||
|
moveFiles: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, [seriesId, dispatch]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContent onModalClose={onModalClose}>
|
||||||
|
<ModalHeader>{translate('EditSeriesModalHeader', { title })}</ModalHeader>
|
||||||
|
|
||||||
|
<ModalBody>
|
||||||
|
<Form {...otherSettings}>
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('Monitored')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.CHECK}
|
||||||
|
name="monitored"
|
||||||
|
helpText={translate('MonitoredEpisodesHelpText')}
|
||||||
|
{...settings.monitored}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>
|
||||||
|
{translate('MonitorNewSeasons')}
|
||||||
|
<Popover
|
||||||
|
anchor={<Icon className={styles.labelIcon} name={icons.INFO} />}
|
||||||
|
title={translate('MonitorNewSeasons')}
|
||||||
|
body={<SeriesMonitorNewItemsOptionsPopoverContent />}
|
||||||
|
position={tooltipPositions.RIGHT}
|
||||||
|
/>
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.MONITOR_NEW_ITEMS_SELECT}
|
||||||
|
name="monitorNewItems"
|
||||||
|
helpText={translate('MonitorNewSeasonsHelpText')}
|
||||||
|
{...settings.monitorNewItems}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('UseSeasonFolder')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.CHECK}
|
||||||
|
name="seasonFolder"
|
||||||
|
helpText={translate('UseSeasonFolderHelpText')}
|
||||||
|
{...settings.seasonFolder}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('QualityProfile')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.QUALITY_PROFILE_SELECT}
|
||||||
|
name="qualityProfileId"
|
||||||
|
{...settings.qualityProfileId}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('SeriesType')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.SERIES_TYPE_SELECT}
|
||||||
|
name="seriesType"
|
||||||
|
{...settings.seriesType}
|
||||||
|
helpText={translate('SeriesTypesHelpText')}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('Path')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.PATH}
|
||||||
|
name="path"
|
||||||
|
{...settings.path}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('Tags')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.TAG}
|
||||||
|
name="tags"
|
||||||
|
{...settings.tags}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</Form>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
className={styles.deleteButton}
|
||||||
|
kind={kinds.DANGER}
|
||||||
|
onPress={onDeleteSeriesPress}
|
||||||
|
>
|
||||||
|
{translate('Delete')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
|
||||||
|
|
||||||
|
<SpinnerErrorButton
|
||||||
|
error={saveError}
|
||||||
|
isSpinning={isSaving}
|
||||||
|
onPress={handleSavePress}
|
||||||
|
>
|
||||||
|
{translate('Save')}
|
||||||
|
</SpinnerErrorButton>
|
||||||
|
</ModalFooter>
|
||||||
|
|
||||||
|
<MoveSeriesModal
|
||||||
|
originalPath={path}
|
||||||
|
destinationPath={pendingChanges.path}
|
||||||
|
isOpen={isConfirmMoveModalOpen}
|
||||||
|
onModalClose={handleCancelPress}
|
||||||
|
onSavePress={handleSavePress}
|
||||||
|
onMoveSeriesPress={handleMoveSeriesPress}
|
||||||
|
/>
|
||||||
|
</ModalContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditSeriesModalContent;
|
@ -1,109 +0,0 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
function getValidationFailures(saveError) {
|
|
||||||
if (!saveError || saveError.status !== 400) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return _.cloneDeep(saveError.responseJSON);
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapFailure(failure) {
|
|
||||||
return {
|
|
||||||
errorMessage: failure.errorMessage,
|
|
||||||
infoLink: failure.infoLink,
|
|
||||||
detailedDescription: failure.detailedDescription,
|
|
||||||
|
|
||||||
// TODO: Remove these renamed properties
|
|
||||||
message: failure.errorMessage,
|
|
||||||
link: failure.infoLink,
|
|
||||||
detailedMessage: failure.detailedDescription
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectSettings(item, pendingChanges, saveError) {
|
|
||||||
const validationFailures = getValidationFailures(saveError);
|
|
||||||
|
|
||||||
// Merge all settings from the item along with pending
|
|
||||||
// changes to ensure any settings that were not included
|
|
||||||
// with the item are included.
|
|
||||||
const allSettings = Object.assign({}, item, pendingChanges);
|
|
||||||
|
|
||||||
const settings = _.reduce(allSettings, (result, value, key) => {
|
|
||||||
if (key === 'fields') {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a flattened value
|
|
||||||
if (key === 'implementationName') {
|
|
||||||
result.implementationName = item[key];
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
const setting = {
|
|
||||||
value: item[key],
|
|
||||||
errors: _.map(_.remove(validationFailures, (failure) => {
|
|
||||||
return failure.propertyName.toLowerCase() === key.toLowerCase() && !failure.isWarning;
|
|
||||||
}), mapFailure),
|
|
||||||
|
|
||||||
warnings: _.map(_.remove(validationFailures, (failure) => {
|
|
||||||
return failure.propertyName.toLowerCase() === key.toLowerCase() && failure.isWarning;
|
|
||||||
}), mapFailure)
|
|
||||||
};
|
|
||||||
|
|
||||||
if (pendingChanges.hasOwnProperty(key)) {
|
|
||||||
setting.previousValue = setting.value;
|
|
||||||
setting.value = pendingChanges[key];
|
|
||||||
setting.pending = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
result[key] = setting;
|
|
||||||
return result;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
const fields = _.reduce(item.fields, (result, f) => {
|
|
||||||
const field = Object.assign({ pending: false }, f);
|
|
||||||
const hasPendingFieldChange = pendingChanges.fields && pendingChanges.fields.hasOwnProperty(field.name);
|
|
||||||
|
|
||||||
if (hasPendingFieldChange) {
|
|
||||||
field.previousValue = field.value;
|
|
||||||
field.value = pendingChanges.fields[field.name];
|
|
||||||
field.pending = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
field.errors = _.map(_.remove(validationFailures, (failure) => {
|
|
||||||
return failure.propertyName.toLowerCase() === field.name.toLowerCase() && !failure.isWarning;
|
|
||||||
}), mapFailure);
|
|
||||||
|
|
||||||
field.warnings = _.map(_.remove(validationFailures, (failure) => {
|
|
||||||
return failure.propertyName.toLowerCase() === field.name.toLowerCase() && failure.isWarning;
|
|
||||||
}), mapFailure);
|
|
||||||
|
|
||||||
result.push(field);
|
|
||||||
return result;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (fields.length) {
|
|
||||||
settings.fields = fields;
|
|
||||||
}
|
|
||||||
|
|
||||||
const validationErrors = _.filter(validationFailures, (failure) => {
|
|
||||||
return !failure.isWarning;
|
|
||||||
});
|
|
||||||
|
|
||||||
const validationWarnings = _.filter(validationFailures, (failure) => {
|
|
||||||
return failure.isWarning;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
settings,
|
|
||||||
validationErrors,
|
|
||||||
validationWarnings,
|
|
||||||
hasPendingChanges: !_.isEmpty(pendingChanges),
|
|
||||||
hasSettings: !_.isEmpty(settings),
|
|
||||||
pendingChanges
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default selectSettings;
|
|
@ -0,0 +1,167 @@
|
|||||||
|
import { cloneDeep, isEmpty } from 'lodash';
|
||||||
|
import { Error } from 'App/State/AppSectionState';
|
||||||
|
import Field from 'typings/Field';
|
||||||
|
import {
|
||||||
|
Failure,
|
||||||
|
Pending,
|
||||||
|
PendingField,
|
||||||
|
PendingSection,
|
||||||
|
ValidationError,
|
||||||
|
ValidationFailure,
|
||||||
|
ValidationWarning,
|
||||||
|
} from 'typings/pending';
|
||||||
|
|
||||||
|
interface ValidationFailures {
|
||||||
|
errors: ValidationError[];
|
||||||
|
warnings: ValidationWarning[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getValidationFailures(saveError?: Error): ValidationFailures {
|
||||||
|
if (!saveError || saveError.status !== 400) {
|
||||||
|
return {
|
||||||
|
errors: [],
|
||||||
|
warnings: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return cloneDeep(saveError.responseJSON as ValidationFailure[]).reduce(
|
||||||
|
(acc: ValidationFailures, failure: ValidationFailure) => {
|
||||||
|
if (failure.isWarning) {
|
||||||
|
acc.warnings.push(failure as ValidationWarning);
|
||||||
|
} else {
|
||||||
|
acc.errors.push(failure as ValidationError);
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
errors: [],
|
||||||
|
warnings: [],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFailures(failures: ValidationFailure[], key: string) {
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
for (let i = failures.length - 1; i >= 0; i--) {
|
||||||
|
if (failures[i].propertyName.toLowerCase() === key.toLowerCase()) {
|
||||||
|
result.unshift(mapFailure(failures[i]));
|
||||||
|
|
||||||
|
failures.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapFailure(failure: ValidationFailure): Failure {
|
||||||
|
return {
|
||||||
|
errorMessage: failure.errorMessage,
|
||||||
|
infoLink: failure.infoLink,
|
||||||
|
detailedDescription: failure.detailedDescription,
|
||||||
|
|
||||||
|
// TODO: Remove these renamed properties
|
||||||
|
message: failure.errorMessage,
|
||||||
|
link: failure.infoLink,
|
||||||
|
detailedMessage: failure.detailedDescription,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ModelBaseSetting {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
[id: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectSettings<T extends ModelBaseSetting>(
|
||||||
|
item: T,
|
||||||
|
pendingChanges: Partial<ModelBaseSetting>,
|
||||||
|
saveError?: Error
|
||||||
|
) {
|
||||||
|
const { errors, warnings } = getValidationFailures(saveError);
|
||||||
|
|
||||||
|
// Merge all settings from the item along with pending
|
||||||
|
// changes to ensure any settings that were not included
|
||||||
|
// with the item are included.
|
||||||
|
const allSettings = Object.assign({}, item, pendingChanges);
|
||||||
|
|
||||||
|
const settings = Object.keys(allSettings).reduce(
|
||||||
|
(acc: PendingSection<T>, key) => {
|
||||||
|
if (key === 'fields') {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a flattened value
|
||||||
|
if (key === 'implementationName') {
|
||||||
|
// acc.implementationName = item[key];
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
const setting: Pending<T> = {
|
||||||
|
value: item[key],
|
||||||
|
errors: getFailures(errors, key),
|
||||||
|
warnings: getFailures(warnings, key),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (pendingChanges.hasOwnProperty(key)) {
|
||||||
|
setting.previousValue = setting.value;
|
||||||
|
setting.value = pendingChanges[key];
|
||||||
|
setting.pending = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error - This is a valid key
|
||||||
|
acc[key] = setting;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as PendingSection<T>
|
||||||
|
);
|
||||||
|
|
||||||
|
if ('fields' in item) {
|
||||||
|
const fields =
|
||||||
|
(item.fields as Field[]).reduce((acc: PendingField<T>[], f) => {
|
||||||
|
const field: PendingField<T> = Object.assign(
|
||||||
|
{ pending: false, errors: [], warnings: [] },
|
||||||
|
f
|
||||||
|
);
|
||||||
|
|
||||||
|
if ('fields' in pendingChanges) {
|
||||||
|
const pendingChangesFields = pendingChanges.fields as Record<
|
||||||
|
string,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
any
|
||||||
|
>;
|
||||||
|
|
||||||
|
if (pendingChangesFields.hasOwnProperty(field.name)) {
|
||||||
|
field.previousValue = field.value;
|
||||||
|
field.value = pendingChangesFields[field.name];
|
||||||
|
field.pending = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
field.errors = getFailures(errors, field.name);
|
||||||
|
field.warnings = getFailures(warnings, field.name);
|
||||||
|
|
||||||
|
acc.push(field);
|
||||||
|
return acc;
|
||||||
|
}, []) ?? [];
|
||||||
|
|
||||||
|
if (fields.length) {
|
||||||
|
settings.fields = fields;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const validationErrors = errors;
|
||||||
|
const validationWarnings = warnings;
|
||||||
|
|
||||||
|
return {
|
||||||
|
settings,
|
||||||
|
validationErrors,
|
||||||
|
validationWarnings,
|
||||||
|
hasPendingChanges: !isEmpty(pendingChanges),
|
||||||
|
hasSettings: !isEmpty(settings),
|
||||||
|
pendingChanges,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default selectSettings;
|
@ -1,19 +1,15 @@
|
|||||||
interface AjaxResponse {
|
import { Error } from 'App/State/AppSectionState';
|
||||||
responseJSON:
|
|
||||||
| {
|
|
||||||
message: string | undefined;
|
|
||||||
}
|
|
||||||
| undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getErrorMessage(xhr: AjaxResponse, fallbackErrorMessage?: string) {
|
function getErrorMessage(xhr: Error, fallbackErrorMessage?: string) {
|
||||||
if (!xhr || !xhr.responseJSON || !xhr.responseJSON.message) {
|
if (!xhr || !xhr.responseJSON) {
|
||||||
return fallbackErrorMessage;
|
return fallbackErrorMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = xhr.responseJSON.message;
|
if ('message' in xhr.responseJSON && xhr.responseJSON.message) {
|
||||||
|
return xhr.responseJSON.message;
|
||||||
|
}
|
||||||
|
|
||||||
return message || fallbackErrorMessage;
|
return fallbackErrorMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default getErrorMessage;
|
export default getErrorMessage;
|
||||||
|
Loading…
Reference in new issue