New: Added Artist Monitoring Toggle to Artist Details

(cherry picked from commit 0ff889c3be1e8ee48759b233aeabf1bf1c4b4ff4)
pull/4227/head
Robin Dadswell 3 years ago committed by Bogdan
parent 6a65539ae6
commit 690b2c72c8

@ -0,0 +1,5 @@
.message {
composes: alert from '~Components/Alert.css';
margin-bottom: 30px;
}

@ -0,0 +1,7 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'message': string;
}
export const cssExports: CssExports;
export default cssExports;

@ -2,14 +2,17 @@ import React from 'react';
import Alert from 'Components/Alert';
import DescriptionList from 'Components/DescriptionList/DescriptionList';
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './ArtistMonitoringOptionsPopoverContent.css';
function ArtistMonitoringOptionsPopoverContent() {
return (
<>
<Alert>
<Alert kind={kinds.INFO} className={styles.message}>
This is a one time adjustment to set which albums are monitored
</Alert>
<DescriptionList>
<DescriptionListItem
title={translate('AllAlbums')}

@ -6,6 +6,7 @@ import ArtistPoster from 'Artist/ArtistPoster';
import DeleteArtistModal from 'Artist/Delete/DeleteArtistModal';
import EditArtistModalConnector from 'Artist/Edit/EditArtistModalConnector';
import ArtistHistoryModal from 'Artist/History/ArtistHistoryModal';
import MonitoringOptionsModal from 'Artist/MonitoringOptions/MonitoringOptionsModal';
import ArtistInteractiveSearchModalConnector from 'Artist/Search/ArtistInteractiveSearchModalConnector';
import HeartRating from 'Components/HeartRating';
import Icon from 'Components/Icon';
@ -72,6 +73,7 @@ class ArtistDetails extends Component {
isArtistHistoryModalOpen: false,
isInteractiveImportModalOpen: false,
isInteractiveSearchModalOpen: false,
isMonitorOptionsModalOpen: false,
allExpanded: false,
allCollapsed: false,
expandedState: {}
@ -148,6 +150,14 @@ class ArtistDetails extends Component {
this.setState({ isArtistHistoryModalOpen: false });
};
onMonitorOptionsPress = () => {
this.setState({ isMonitorOptionsModalOpen: true });
};
onMonitorOptionsClose = () => {
this.setState({ isMonitorOptionsModalOpen: false });
};
onExpandAllPress = () => {
const {
allExpanded,
@ -224,6 +234,7 @@ class ArtistDetails extends Component {
isArtistHistoryModalOpen,
isInteractiveImportModalOpen,
isInteractiveSearchModalOpen,
isMonitorOptionsModalOpen,
allExpanded,
allCollapsed,
expandedState
@ -319,6 +330,12 @@ class ArtistDetails extends Component {
<PageToolbarSeparator />
<PageToolbarButton
label={translate('ArtistMonitoring')}
iconName={icons.MONITORED}
onPress={this.onMonitorOptionsPress}
/>
<PageToolbarButton
label={translate('Edit')}
iconName={icons.EDIT}
@ -330,6 +347,7 @@ class ArtistDetails extends Component {
iconName={icons.DELETE}
onPress={this.onDeleteArtistPress}
/>
</PageToolbarSection>
<PageToolbarSection alignContent={align.RIGHT}>
@ -689,6 +707,12 @@ class ArtistDetails extends Component {
artistId={id}
onModalClose={this.onInteractiveSearchModalClose}
/>
<MonitoringOptionsModal
isOpen={isMonitorOptionsModalOpen}
artistId={id}
onModalClose={this.onMonitorOptionsClose}
/>
</PageContentBody>
</PageContent>
);

@ -0,0 +1,39 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { clearPendingChanges } from 'Store/Actions/baseActions';
import MonitoringOptionsModal from './MonitoringOptionsModal';
const mapDispatchToProps = {
clearPendingChanges
};
class MonitoringOptionsModalConnector extends Component {
//
// Listeners
onModalClose = () => {
this.props.clearPendingChanges({ section: 'artist' });
this.props.onModalClose();
};
//
// Render
render() {
return (
<MonitoringOptionsModal
{...this.props}
onModalClose={this.onModalClose}
/>
);
}
}
MonitoringOptionsModalConnector.propTypes = {
onModalClose: PropTypes.func.isRequired,
clearPendingChanges: PropTypes.func.isRequired
};
export default connect(undefined, mapDispatchToProps)(MonitoringOptionsModalConnector);

@ -0,0 +1,25 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import MonitoringOptionsModalContentConnector from './MonitoringOptionsModalContentConnector';
function MonitoringOptionsModal({ isOpen, onModalClose, ...otherProps }) {
return (
<Modal
isOpen={isOpen}
onModalClose={onModalClose}
>
<MonitoringOptionsModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
</Modal>
);
}
MonitoringOptionsModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default MonitoringOptionsModal;

@ -0,0 +1,9 @@
.labelIcon {
margin-left: 8px;
}
.message {
composes: alert from '~Components/Alert.css';
margin-bottom: 30px;
}

@ -0,0 +1,8 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'labelIcon': string;
'message': string;
}
export const cssExports: CssExports;
export default cssExports;

@ -1,18 +1,22 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import ArtistMonitoringOptionsPopoverContent from 'AddArtist/ArtistMonitoringOptionsPopoverContent';
import Alert from 'Components/Alert';
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 { inputTypes, kinds } from 'Helpers/Props';
import Popover from 'Components/Tooltip/Popover';
import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './MonitoringOptionsModalContent.css';
const NO_CHANGE = 'noChange';
@ -51,8 +55,7 @@ class MonitoringOptionsModalContent extends Component {
onSavePress = () => {
const {
onSavePress,
isSaving
onSavePress
} = this.props;
const {
monitor
@ -61,14 +64,6 @@ class MonitoringOptionsModalContent extends Component {
if (monitor !== NO_CHANGE) {
onSavePress({ monitor });
}
if (!isSaving) {
this.onModalClose();
}
};
onModalClose = () => {
this.props.onModalClose();
};
//
@ -89,19 +84,31 @@ class MonitoringOptionsModalContent extends Component {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{translate('MonitorAlbum')}
{translate('MonitorArtist')}
</ModalHeader>
<ModalBody>
<Alert kind={kinds.INFO}>
<div>
{translate('MonitorAlbumExistingOnlyWarning')}
</div>
<Alert kind={kinds.INFO} className={styles.message}>
{translate('MonitorAlbumExistingOnlyWarning')}
</Alert>
<Form {...otherProps}>
<FormGroup>
<FormLabel>{translate('Monitoring')}</FormLabel>
<FormLabel>
{translate('MonitorExistingAlbums')}
<Popover
anchor={
<Icon
className={styles.labelIcon}
name={icons.INFO}
/>
}
title={translate('MonitoringOptions')}
body={<ArtistMonitoringOptionsPopoverContent />}
position={tooltipPositions.RIGHT}
/>
</FormLabel>
<FormInputGroup
type={inputTypes.MONITOR_ALBUMS_SELECT}
@ -134,7 +141,7 @@ class MonitoringOptionsModalContent extends Component {
}
MonitoringOptionsModalContent.propTypes = {
authorId: PropTypes.number.isRequired,
artistId: PropTypes.number.isRequired,
saveError: PropTypes.object,
isSaving: PropTypes.bool.isRequired,
onInputChange: PropTypes.func.isRequired,

@ -0,0 +1,76 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { updateArtistsMonitor } from 'Store/Actions/artistActions';
import MonitoringOptionsModalContent from './MonitoringOptionsModalContent';
function createMapStateToProps() {
return createSelector(
(state) => state.artist,
(artistState) => {
const {
isSaving,
saveError
} = artistState;
return {
isSaving,
saveError
};
}
);
}
const mapDispatchToProps = {
dispatchUpdateMonitoringOptions: updateArtistsMonitor
};
class MonitoringOptionsModalContentConnector extends Component {
//
// Lifecycle
componentDidUpdate(prevProps, prevState) {
if (prevProps.isSaving && !this.props.isSaving && !this.props.saveError) {
this.props.onModalClose(true);
}
}
//
// Listeners
onInputChange = ({ name, value }) => {
this.setState({ name, value });
};
onSavePress = ({ monitor }) => {
this.props.dispatchUpdateMonitoringOptions({
artistIds: [this.props.artistId],
monitor
});
};
//
// Render
render() {
return (
<MonitoringOptionsModalContent
{...this.props}
onInputChange={this.onInputChange}
onSavePress={this.onSavePress}
/>
);
}
}
MonitoringOptionsModalContentConnector.propTypes = {
artistId: PropTypes.number.isRequired,
isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object,
dispatchUpdateMonitoringOptions: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(MonitoringOptionsModalContentConnector);

@ -102,21 +102,21 @@ export const actionHandlers = handleThunks({
[SAVE_ALBUM_STUDIO]: function(getState, payload, dispatch) {
const {
artistIds,
monitored,
monitor,
monitored,
monitorNewItems
} = payload;
const artist = [];
const artists = [];
artistIds.forEach((id) => {
const artistToUpdate = { id };
const artistsToUpdate = { id };
if (payload.hasOwnProperty('monitored')) {
artistToUpdate.monitored = monitored;
artistsToUpdate.monitored = monitored;
}
artist.push(artistToUpdate);
artists.push(artistsToUpdate);
});
dispatch(set({
@ -128,7 +128,7 @@ export const actionHandlers = handleThunks({
url: '/albumStudio',
method: 'POST',
data: JSON.stringify({
artist,
artist: artists,
monitoringOptions: { monitor },
monitorNewItems
}),

@ -2,11 +2,12 @@ import _ from 'lodash';
import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions';
import { filterTypePredicates, filterTypes, sortDirections } from 'Helpers/Props';
import { fetchAlbums } from 'Store/Actions/albumActions';
import { createThunk, handleThunks } from 'Store/thunks';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import dateFilterPredicate from 'Utilities/Date/dateFilterPredicate';
import translate from 'Utilities/String/translate';
import { updateItem } from './baseActions';
import { set, updateItem } from './baseActions';
import createFetchHandler from './Creators/createFetchHandler';
import createHandleActions from './Creators/createHandleActions';
import createRemoveItemHandler from './Creators/createRemoveItemHandler';
@ -177,6 +178,7 @@ export const DELETE_ARTIST = 'artist/deleteArtist';
export const TOGGLE_ARTIST_MONITORED = 'artist/toggleArtistMonitored';
export const TOGGLE_ALBUM_MONITORED = 'artist/toggleAlbumMonitored';
export const UPDATE_ARTISTS_MONITOR = 'artist/updateArtistsMonitor';
export const SET_DELETE_OPTION = 'artist/setDeleteOption';
@ -212,6 +214,7 @@ export const deleteArtist = createThunk(DELETE_ARTIST, (payload) => {
export const toggleArtistMonitored = createThunk(TOGGLE_ARTIST_MONITORED);
export const toggleAlbumMonitored = createThunk(TOGGLE_ALBUM_MONITORED);
export const updateArtistsMonitor = createThunk(UPDATE_ARTISTS_MONITOR);
export const setArtistValue = createAction(SET_ARTIST_VALUE, (payload) => {
return {
@ -342,6 +345,61 @@ export const actionHandlers = handleThunks({
seasons: artist.seasons
}));
});
},
[UPDATE_ARTISTS_MONITOR]: function(getState, payload, dispatch) {
const {
artistIds,
monitor,
monitored,
monitorNewItems
} = payload;
const artists = [];
artistIds.forEach((id) => {
const artistsToUpdate = { id };
if (monitored != null) {
artistsToUpdate.monitored = monitored;
}
artists.push(artistsToUpdate);
});
dispatch(set({
section,
isSaving: true
}));
const promise = createAjaxRequest({
url: '/albumStudio',
method: 'POST',
data: JSON.stringify({
artist: artists,
monitoringOptions: { monitor },
monitorNewItems
}),
dataType: 'json'
}).request;
promise.done((data) => {
dispatch(fetchAlbums({ artistId: artistIds[0] }));
dispatch(set({
section,
isSaving: false,
saveError: null
}));
});
promise.fail((xhr) => {
dispatch(set({
section,
isSaving: false,
saveError: xhr
}));
});
}
});

@ -98,6 +98,7 @@
"ArtistClickToChangeAlbum": "Click to change album",
"ArtistEditor": "Artist Editor",
"ArtistFolderFormat": "Artist Folder Format",
"ArtistMonitoring": "Artist Monitoring",
"ArtistName": "Artist Name",
"ArtistNameHelpText": "The name of the artist/album to exclude (can be anything meaningful)",
"ArtistProgressBarText": "{trackFileCount} / {trackCount} (Total: {totalTrackCount})",

Loading…
Cancel
Save