New: Write metadata to tags, with UI for previewing changes (#633)

This commit is contained in:
ta264
2019-03-15 12:10:45 +00:00
committed by GitHub
parent 6548f4b1b7
commit 072f772dc8
82 changed files with 2938 additions and 358 deletions
@@ -23,6 +23,7 @@ import Popover from 'Components/Tooltip/Popover';
import Tooltip from 'Components/Tooltip/Tooltip';
import TrackFileEditorModal from 'TrackFile/Editor/TrackFileEditorModal';
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
import RetagPreviewModalConnector from 'Retag/RetagPreviewModalConnector';
import QualityProfileNameConnector from 'Settings/Profiles/Quality/QualityProfileNameConnector';
import ArtistPoster from 'Artist/ArtistPoster';
import EditArtistModalConnector from 'Artist/Edit/EditArtistModalConnector';
@@ -66,6 +67,7 @@ class ArtistDetails extends Component {
this.state = {
isOrganizeModalOpen: false,
isRetagModalOpen: false,
isManageTracksOpen: false,
isEditArtistModalOpen: false,
isDeleteArtistModalOpen: false,
@@ -89,6 +91,14 @@ class ArtistDetails extends Component {
this.setState({ isOrganizeModalOpen: false });
}
onRetagPress = () => {
this.setState({ isRetagModalOpen: true });
}
onRetagModalClose = () => {
this.setState({ isRetagModalOpen: false });
}
onManageTracksPress = () => {
this.setState({ isManageTracksOpen: true });
}
@@ -207,6 +217,7 @@ class ArtistDetails extends Component {
const {
isOrganizeModalOpen,
isRetagModalOpen,
isManageTracksOpen,
isEditArtistModalOpen,
isDeleteArtistModalOpen,
@@ -276,6 +287,12 @@ class ArtistDetails extends Component {
onPress={this.onOrganizePress}
/>
<PageToolbarButton
label="Preview Retag"
iconName={icons.RETAG}
onPress={this.onRetagPress}
/>
<PageToolbarButton
label="Manage Tracks"
iconName={icons.TRACK_FILE}
@@ -600,6 +617,12 @@ class ArtistDetails extends Component {
onModalClose={this.onOrganizeModalClose}
/>
<RetagPreviewModalConnector
isOpen={isRetagModalOpen}
artistId={id}
onModalClose={this.onRetagModalClose}
/>
<TrackFileEditorModal
isOpen={isManageTracksOpen}
artistId={id}
@@ -14,6 +14,7 @@ import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import NoArtist from 'Artist/NoArtist';
import OrganizeArtistModal from './Organize/OrganizeArtistModal';
import RetagArtistModal from './AudioTags/RetagArtistModal';
import ArtistEditorRowConnector from './ArtistEditorRowConnector';
import ArtistEditorFooter from './ArtistEditorFooter';
import ArtistEditorFilterModalConnector from './ArtistEditorFilterModalConnector';
@@ -84,6 +85,7 @@ class ArtistEditor extends Component {
lastToggled: null,
selectedState: {},
isOrganizingArtistModalOpen: false,
isRetaggingArtistModalOpen: false,
columns: getColumns(props.showLanguageProfile, props.showMetadataProfile)
};
}
@@ -142,6 +144,18 @@ class ArtistEditor extends Component {
}
}
onRetagArtistPress = () => {
this.setState({ isRetaggingArtistModalOpen: true });
}
onRetagArtistModalClose = (organized) => {
this.setState({ isRetaggingArtistModalOpen: false });
if (organized === true) {
this.onSelectAllChange({ value: false });
}
}
//
// Render
@@ -162,6 +176,7 @@ class ArtistEditor extends Component {
isDeleting,
deleteError,
isOrganizingArtist,
isRetaggingArtist,
showLanguageProfile,
showMetadataProfile,
onSortPress,
@@ -250,10 +265,12 @@ class ArtistEditor extends Component {
isDeleting={isDeleting}
deleteError={deleteError}
isOrganizingArtist={isOrganizingArtist}
isRetaggingArtist={isRetaggingArtist}
showLanguageProfile={showLanguageProfile}
showMetadataProfile={showMetadataProfile}
onSaveSelected={this.onSaveSelected}
onOrganizeArtistPress={this.onOrganizeArtistPress}
onRetagArtistPress={this.onRetagArtistPress}
/>
<OrganizeArtistModal
@@ -261,6 +278,13 @@ class ArtistEditor extends Component {
artistIds={selectedArtistIds}
onModalClose={this.onOrganizeArtistModalClose}
/>
<RetagArtistModal
isOpen={this.state.isRetaggingArtistModalOpen}
artistIds={selectedArtistIds}
onModalClose={this.onRetagArtistModalClose}
/>
</PageContent>
);
}
@@ -282,6 +306,7 @@ ArtistEditor.propTypes = {
isDeleting: PropTypes.bool.isRequired,
deleteError: PropTypes.object,
isOrganizingArtist: PropTypes.bool.isRequired,
isRetaggingArtist: PropTypes.bool.isRequired,
showLanguageProfile: PropTypes.bool.isRequired,
showMetadataProfile: PropTypes.bool.isRequired,
onSortPress: PropTypes.func.isRequired,
@@ -16,9 +16,11 @@ function createMapStateToProps() {
(state) => state.settings.metadataProfiles,
createClientSideCollectionSelector('artist', 'artistEditor'),
createCommandExecutingSelector(commandNames.RENAME_ARTIST),
(languageProfiles, metadataProfiles, artist, isOrganizingArtist) => {
createCommandExecutingSelector(commandNames.RETAG_ARTIST),
(languageProfiles, metadataProfiles, artist, isOrganizingArtist, isRetaggingArtist) => {
return {
isOrganizingArtist,
isRetaggingArtist,
showLanguageProfile: languageProfiles.items.length > 1,
showMetadataProfile: metadataProfiles.items.length > 1,
...artist
@@ -145,9 +145,11 @@ class ArtistEditorFooter extends Component {
isSaving,
isDeleting,
isOrganizingArtist,
isRetaggingArtist,
showLanguageProfile,
showMetadataProfile,
onOrganizeArtistPress
onOrganizeArtistPress,
onRetagArtistPress
} = this.props;
const {
@@ -288,19 +290,29 @@ class ArtistEditorFooter extends Component {
className={styles.organizeSelectedButton}
kind={kinds.WARNING}
isSpinning={isOrganizingArtist}
isDisabled={!selectedCount || isOrganizingArtist}
isDisabled={!selectedCount || isOrganizingArtist || isRetaggingArtist}
onPress={onOrganizeArtistPress}
>
Rename Files
</SpinnerButton>
<SpinnerButton
className={styles.organizeSelectedButton}
kind={kinds.WARNING}
isSpinning={isRetaggingArtist}
isDisabled={!selectedCount || isOrganizingArtist || isRetaggingArtist}
onPress={onRetagArtistPress}
>
Write Metadata Tags
</SpinnerButton>
<SpinnerButton
className={styles.tagsButton}
isSpinning={isSaving && savingTags}
isDisabled={!selectedCount || isOrganizingArtist}
isDisabled={!selectedCount || isOrganizingArtist || isRetaggingArtist}
onPress={this.onTagsPress}
>
Set Tags
Set Lidarr Tags
</SpinnerButton>
</div>
@@ -350,10 +362,12 @@ ArtistEditorFooter.propTypes = {
isDeleting: PropTypes.bool.isRequired,
deleteError: PropTypes.object,
isOrganizingArtist: PropTypes.bool.isRequired,
isRetaggingArtist: PropTypes.bool.isRequired,
showLanguageProfile: PropTypes.bool.isRequired,
showMetadataProfile: PropTypes.bool.isRequired,
onSaveSelected: PropTypes.func.isRequired,
onOrganizeArtistPress: PropTypes.func.isRequired
onOrganizeArtistPress: PropTypes.func.isRequired,
onRetagArtistPress: PropTypes.func.isRequired
};
export default ArtistEditorFooter;
@@ -0,0 +1,31 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import RetagArtistModalContentConnector from './RetagArtistModalContentConnector';
function RetagArtistModal(props) {
const {
isOpen,
onModalClose,
...otherProps
} = props;
return (
<Modal
isOpen={isOpen}
onModalClose={onModalClose}
>
<RetagArtistModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
</Modal>
);
}
RetagArtistModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default RetagArtistModal;
@@ -0,0 +1,8 @@
.retagIcon {
margin-left: 5px;
}
.message {
margin-top: 20px;
margin-bottom: 10px;
}
@@ -0,0 +1,73 @@
import PropTypes from 'prop-types';
import React from 'react';
import { icons, kinds } from 'Helpers/Props';
import Alert from 'Components/Alert';
import Button from 'Components/Link/Button';
import Icon from 'Components/Icon';
import ModalContent from 'Components/Modal/ModalContent';
import ModalHeader from 'Components/Modal/ModalHeader';
import ModalBody from 'Components/Modal/ModalBody';
import ModalFooter from 'Components/Modal/ModalFooter';
import styles from './RetagArtistModalContent.css';
function RetagArtistModalContent(props) {
const {
artistNames,
onModalClose,
onRetagArtistPress
} = props;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Retag Selected Artist
</ModalHeader>
<ModalBody>
<Alert>
Tip: To preview the tags that will be written... select "Cancel" then click any artist name and use the
<Icon
className={styles.retagIcon}
name={icons.RETAG}
/>
</Alert>
<div className={styles.message}>
Are you sure you want to re-tag all files in the {artistNames.length} selected artist?
</div>
<ul>
{
artistNames.map((artistName) => {
return (
<li key={artistName}>
{artistName}
</li>
);
})
}
</ul>
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>
Cancel
</Button>
<Button
kind={kinds.DANGER}
onPress={onRetagArtistPress}
>
Retag
</Button>
</ModalFooter>
</ModalContent>
);
}
RetagArtistModalContent.propTypes = {
artistNames: PropTypes.arrayOf(PropTypes.string).isRequired,
onModalClose: PropTypes.func.isRequired,
onRetagArtistPress: PropTypes.func.isRequired
};
export default RetagArtistModalContent;
@@ -0,0 +1,67 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
import { executeCommand } from 'Store/Actions/commandActions';
import * as commandNames from 'Commands/commandNames';
import RetagArtistModalContent from './RetagArtistModalContent';
function createMapStateToProps() {
return createSelector(
(state, { artistIds }) => artistIds,
createAllArtistSelector(),
(artistIds, allArtists) => {
const artist = _.intersectionWith(allArtists, artistIds, (s, id) => {
return s.id === id;
});
const sortedArtist = _.orderBy(artist, 'sortName');
const artistNames = _.map(sortedArtist, 'artistName');
return {
artistNames
};
}
);
}
const mapDispatchToProps = {
executeCommand
};
class RetagArtistModalContentConnector extends Component {
//
// Listeners
onRetagArtistPress = () => {
this.props.executeCommand({
name: commandNames.RETAG_ARTIST,
artistIds: this.props.artistIds
});
this.props.onModalClose(true);
}
//
// Render
render(props) {
return (
<RetagArtistModalContent
{...this.props}
onRetagArtistPress={this.onRetagArtistPress}
/>
);
}
}
RetagArtistModalContentConnector.propTypes = {
artistIds: PropTypes.arrayOf(PropTypes.number).isRequired,
onModalClose: PropTypes.func.isRequired,
executeCommand: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(RetagArtistModalContentConnector);