diff --git a/frontend/src/AddArtist/AddNewArtist/AddNewArtistModalContent.js b/frontend/src/AddArtist/AddNewArtist/AddNewArtistModalContent.js deleted file mode 100644 index 2278812b8..000000000 --- a/frontend/src/AddArtist/AddNewArtist/AddNewArtistModalContent.js +++ /dev/null @@ -1,241 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import TextTruncate from 'react-text-truncate'; -import { icons, kinds, inputTypes, tooltipPositions } from 'Helpers/Props'; -import Icon from 'Components/Icon'; -import SpinnerButton from 'Components/Link/SpinnerButton'; -import Form from 'Components/Form/Form'; -import FormGroup from 'Components/Form/FormGroup'; -import FormLabel from 'Components/Form/FormLabel'; -import FormInputGroup from 'Components/Form/FormInputGroup'; -import CheckInput from 'Components/Form/CheckInput'; -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 Popover from 'Components/Tooltip/Popover'; -import ArtistPoster from 'Artist/ArtistPoster'; -import ArtistMonitoringOptionsPopoverContent from 'AddArtist/ArtistMonitoringOptionsPopoverContent'; -import styles from './AddNewArtistModalContent.css'; - -class AddNewArtistModalContent extends Component { - - // - // Lifecycle - - constructor(props, context) { - super(props, context); - - this.state = { - searchForMissingAlbums: false - }; - } - - // - // Listeners - - onSearchForMissingAlbumsChange = ({ value }) => { - this.setState({ searchForMissingAlbums: value }); - } - - onQualityProfileIdChange = ({ value }) => { - this.props.onInputChange({ name: 'qualityProfileId', value: parseInt(value) }); - } - - onMetadataProfileIdChange = ({ value }) => { - this.props.onInputChange({ name: 'metadataProfileId', value: parseInt(value) }); - } - - onAddArtistPress = () => { - this.props.onAddArtistPress(this.state.searchForMissingAlbums); - } - - // - // Render - - render() { - const { - artistName, - overview, - images, - isAdding, - rootFolderPath, - monitor, - qualityProfileId, - metadataProfileId, - albumFolder, - tags, - showMetadataProfile, - isSmallScreen, - onModalClose, - onInputChange, - ...otherProps - } = this.props; - - return ( - - - {artistName} - - - -
- { - isSmallScreen ? - null: -
- -
- } - -
- { - overview ? -
- -
: - null - } - -
- - Root Folder - - - - - - - Monitor - - - } - title="Monitoring Options" - body={} - position={tooltipPositions.RIGHT} - /> - - - - - - - Quality Profile - - - - - - Metadata Profile - - - - - - Album Folder - - - - - - Tags - - - -
-
-
-
- - - - - - Add {artistName} - - -
- ); - } -} - -AddNewArtistModalContent.propTypes = { - artistName: PropTypes.string.isRequired, - overview: PropTypes.string, - images: PropTypes.arrayOf(PropTypes.object).isRequired, - isAdding: PropTypes.bool.isRequired, - addError: PropTypes.object, - rootFolderPath: PropTypes.object, - monitor: PropTypes.object.isRequired, - qualityProfileId: PropTypes.object, - metadataProfileId: PropTypes.object, - albumFolder: PropTypes.object.isRequired, - tags: PropTypes.object.isRequired, - showMetadataProfile: PropTypes.bool.isRequired, - isSmallScreen: PropTypes.bool.isRequired, - onModalClose: PropTypes.func.isRequired, - onInputChange: PropTypes.func.isRequired, - onAddArtistPress: PropTypes.func.isRequired -}; - -export default AddNewArtistModalContent; diff --git a/frontend/src/AddArtist/ArtistMetadataProfilePopoverContent.js b/frontend/src/AddArtist/ArtistMetadataProfilePopoverContent.js new file mode 100644 index 000000000..134f1eb1f --- /dev/null +++ b/frontend/src/AddArtist/ArtistMetadataProfilePopoverContent.js @@ -0,0 +1,11 @@ +import React from 'react'; + +function ArtistMetadataProfilePopoverContent() { + return ( +
+ Select 'None' to only include items manually added via search +
+ ); +} + +export default ArtistMetadataProfilePopoverContent; diff --git a/frontend/src/AddArtist/ImportArtist/Import/ImportArtistConnector.js b/frontend/src/AddArtist/ImportArtist/Import/ImportArtistConnector.js index 4ce182bbd..65bcc9645 100644 --- a/frontend/src/AddArtist/ImportArtist/Import/ImportArtistConnector.js +++ b/frontend/src/AddArtist/ImportArtist/Import/ImportArtistConnector.js @@ -6,7 +6,7 @@ import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import { setImportArtistValue, importArtist, clearImportArtist } from 'Store/Actions/importArtistActions'; import { fetchRootFolders } from 'Store/Actions/rootFolderActions'; -import { setAddArtistDefault } from 'Store/Actions/addArtistActions'; +import { setAddDefault } from 'Store/Actions/searchActions'; import createRouteMatchShape from 'Helpers/Props/Shapes/createRouteMatchShape'; import ImportArtist from './ImportArtist'; @@ -67,7 +67,7 @@ const mapDispatchToProps = { dispatchImportArtist: importArtist, dispatchClearImportArtist: clearImportArtist, dispatchFetchRootFolders: fetchRootFolders, - dispatchSetAddArtistDefault: setAddArtistDefault + dispatchSetAddDefault: setAddDefault }; class ImportArtistConnector extends Component { @@ -82,7 +82,7 @@ class ImportArtistConnector extends Component { defaultQualityProfileId, defaultMetadataProfileId, dispatchFetchRootFolders, - dispatchSetAddArtistDefault + dispatchSetAddDefault } = this.props; if (!this.props.rootFoldersPopulated) { @@ -109,7 +109,7 @@ class ImportArtistConnector extends Component { } if (setDefaults) { - dispatchSetAddArtistDefault(setDefaultPayload); + dispatchSetAddDefault(setDefaultPayload); } } @@ -121,7 +121,7 @@ class ImportArtistConnector extends Component { // Listeners onInputChange = (ids, name, value) => { - this.props.dispatchSetAddArtistDefault({ [name]: value }); + this.props.dispatchSetAddDefault({ [name]: value }); ids.forEach((id) => { this.props.dispatchSetImportArtistValue({ @@ -164,7 +164,7 @@ ImportArtistConnector.propTypes = { dispatchImportArtist: PropTypes.func.isRequired, dispatchClearImportArtist: PropTypes.func.isRequired, dispatchFetchRootFolders: PropTypes.func.isRequired, - dispatchSetAddArtistDefault: PropTypes.func.isRequired + dispatchSetAddDefault: PropTypes.func.isRequired }; export default connect(createMapStateToProps, mapDispatchToProps)(ImportArtistConnector); diff --git a/frontend/src/Album/Delete/DeleteAlbumModal.js b/frontend/src/Album/Delete/DeleteAlbumModal.js new file mode 100644 index 000000000..a18644d64 --- /dev/null +++ b/frontend/src/Album/Delete/DeleteAlbumModal.js @@ -0,0 +1,33 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import { sizes } from 'Helpers/Props'; +import Modal from 'Components/Modal/Modal'; +import DeleteAlbumModalContentConnector from './DeleteAlbumModalContentConnector'; + +function DeleteAlbumModal(props) { + const { + isOpen, + onModalClose, + ...otherProps + } = props; + + return ( + + + + ); +} + +DeleteAlbumModal.propTypes = { + isOpen: PropTypes.bool.isRequired, + onModalClose: PropTypes.func.isRequired +}; + +export default DeleteAlbumModal; diff --git a/frontend/src/Album/Delete/DeleteAlbumModalContent.css b/frontend/src/Album/Delete/DeleteAlbumModalContent.css new file mode 100644 index 000000000..dbfef0871 --- /dev/null +++ b/frontend/src/Album/Delete/DeleteAlbumModalContent.css @@ -0,0 +1,12 @@ +.pathContainer { + margin-bottom: 20px; +} + +.pathIcon { + margin-right: 8px; +} + +.deleteFilesMessage { + margin-top: 20px; + color: $dangerColor; +} diff --git a/frontend/src/Album/Delete/DeleteAlbumModalContent.js b/frontend/src/Album/Delete/DeleteAlbumModalContent.js new file mode 100644 index 000000000..b6bd215fe --- /dev/null +++ b/frontend/src/Album/Delete/DeleteAlbumModalContent.js @@ -0,0 +1,157 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import formatBytes from 'Utilities/Number/formatBytes'; +import { inputTypes, kinds } from 'Helpers/Props'; +import Button from 'Components/Link/Button'; +import FormGroup from 'Components/Form/FormGroup'; +import FormLabel from 'Components/Form/FormLabel'; +import FormInputGroup from 'Components/Form/FormInputGroup'; +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 './DeleteAlbumModalContent.css'; + +class DeleteAlbumModalContent extends Component { + + // + // Lifecycle + + constructor(props, context) { + super(props, context); + + this.state = { + deleteFiles: false, + addImportListExclusion: true + }; + } + + // + // Listeners + + onDeleteFilesChange = ({ value }) => { + this.setState({ deleteFiles: value }); + } + + onAddImportListExclusionChange = ({ value }) => { + this.setState({ addImportListExclusion: value }); + } + + onDeleteAlbumConfirmed = () => { + const deleteFiles = this.state.deleteFiles; + const addImportListExclusion = this.state.addImportListExclusion; + + this.setState({ deleteFiles: false }); + this.setState({ addImportListExclusion: false }); + this.props.onDeletePress(deleteFiles, addImportListExclusion); + } + + // + // Render + + render() { + const { + title, + statistics, + onModalClose + } = this.props; + + const { + trackFileCount, + sizeOnDisk + } = statistics; + + const deleteFiles = this.state.deleteFiles; + const addImportListExclusion = this.state.addImportListExclusion; + + const deleteFilesLabel = `Delete ${trackFileCount} Track Files`; + const deleteFilesHelpText = 'Delete the track files'; + + return ( + + + Delete - {title} + + + + + + {deleteFilesLabel} + + + + + + Add List Exclusion + + + + + { + !addImportListExclusion && +
+
If you don't add an import list exclusion and the artist has a metadata profile other than 'None' then this album may be re-added during the next artist refresh.
+
+ } + + { + deleteFiles && +
+
The album's files will be deleted.
+ + { + !!trackFileCount && +
{trackFileCount} track files totaling {formatBytes(sizeOnDisk)}
+ } +
+ } + +
+ + + + + + +
+ ); + } +} + +DeleteAlbumModalContent.propTypes = { + title: PropTypes.string.isRequired, + statistics: PropTypes.object.isRequired, + onDeletePress: PropTypes.func.isRequired, + onModalClose: PropTypes.func.isRequired +}; + +DeleteAlbumModalContent.defaultProps = { + statistics: { + trackFileCount: 0 + } +}; + +export default DeleteAlbumModalContent; diff --git a/frontend/src/Album/Delete/DeleteAlbumModalContentConnector.js b/frontend/src/Album/Delete/DeleteAlbumModalContentConnector.js new file mode 100644 index 000000000..f31b4ac65 --- /dev/null +++ b/frontend/src/Album/Delete/DeleteAlbumModalContentConnector.js @@ -0,0 +1,62 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import { push } from 'connected-react-router'; +import createAlbumSelector from 'Store/Selectors/createAlbumSelector'; +import { deleteAlbum } from 'Store/Actions/albumActions'; +import DeleteAlbumModalContent from './DeleteAlbumModalContent'; + +function createMapStateToProps() { + return createSelector( + createAlbumSelector(), + (album) => { + return album; + } + ); +} + +const mapDispatchToProps = { + push, + deleteAlbum +}; + +class DeleteAlbumModalContentConnector extends Component { + + // + // Listeners + + onDeletePress = (deleteFiles, addImportListExclusion) => { + this.props.deleteAlbum({ + id: this.props.albumId, + deleteFiles, + addImportListExclusion + }); + + this.props.onModalClose(true); + + this.props.push(`${window.Lidarr.urlBase}/artist/${this.props.foreignArtistId}`); + } + + // + // Render + + render() { + return ( + + ); + } +} + +DeleteAlbumModalContentConnector.propTypes = { + albumId: PropTypes.number.isRequired, + foreignArtistId: PropTypes.string.isRequired, + push: PropTypes.func.isRequired, + onModalClose: PropTypes.func.isRequired, + deleteAlbum: PropTypes.func.isRequired +}; + +export default connect(createMapStateToProps, mapDispatchToProps)(DeleteAlbumModalContentConnector); diff --git a/frontend/src/Album/Details/AlbumDetails.js b/frontend/src/Album/Details/AlbumDetails.js index f2288b00f..857f47e1d 100644 --- a/frontend/src/Album/Details/AlbumDetails.js +++ b/frontend/src/Album/Details/AlbumDetails.js @@ -18,6 +18,7 @@ import AlbumCover from 'Album/AlbumCover'; import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector'; import RetagPreviewModalConnector from 'Retag/RetagPreviewModalConnector'; import EditAlbumModalConnector from 'Album/Edit/EditAlbumModalConnector'; +import DeleteAlbumModal from 'Album/Delete/DeleteAlbumModal'; import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import PageContent from 'Components/Page/PageContent'; import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector'; @@ -88,6 +89,7 @@ class AlbumDetails extends Component { isInteractiveSearchModalOpen: false, isManageTracksOpen: false, isEditAlbumModalOpen: false, + isDeleteAlbumModalOpen: false, allExpanded: false, allCollapsed: false, expandedState: {} @@ -121,6 +123,17 @@ class AlbumDetails extends Component { this.setState({ isEditAlbumModalOpen: false }); } + onDeleteAlbumPress = () => { + this.setState({ + isEditAlbumModalOpen: false, + isDeleteAlbumModalOpen: true + }); + } + + onDeleteAlbumModalClose = () => { + this.setState({ isDeleteAlbumModalOpen: false }); + } + onManageTracksPress = () => { this.setState({ isManageTracksOpen: true }); } @@ -208,6 +221,7 @@ class AlbumDetails extends Component { isArtistHistoryModalOpen, isInteractiveSearchModalOpen, isEditAlbumModalOpen, + isDeleteAlbumModalOpen, isManageTracksOpen, allExpanded, allCollapsed, @@ -276,6 +290,12 @@ class AlbumDetails extends Component { onPress={this.onEditAlbumPress} /> + + + + diff --git a/frontend/src/Album/Details/AlbumDetailsPageConnector.js b/frontend/src/Album/Details/AlbumDetailsPageConnector.js index fffd014ad..320348b9b 100644 --- a/frontend/src/Album/Details/AlbumDetailsPageConnector.js +++ b/frontend/src/Album/Details/AlbumDetailsPageConnector.js @@ -1,3 +1,4 @@ +import _ from 'lodash'; import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { connect } from 'react-redux'; @@ -20,6 +21,18 @@ function createMapStateToProps() { const isFetching = albums.isFetching || artist.isFetching; const isPopulated = albums.isPopulated && artist.isPopulated; + // if albums have been fetched, make sure requested one exists + // otherwise don't map foreignAlbumId to trigger not found page + if (!isFetching && isPopulated) { + const albumIndex = _.findIndex(albums.items, { foreignAlbumId }); + if (albumIndex === -1) { + return { + isFetching, + isPopulated + }; + } + } + return { foreignAlbumId, isFetching, diff --git a/frontend/src/App/AppRoutes.js b/frontend/src/App/AppRoutes.js index ed55547e0..19c4785b1 100644 --- a/frontend/src/App/AppRoutes.js +++ b/frontend/src/App/AppRoutes.js @@ -5,7 +5,7 @@ import getPathWithUrlBase from 'Utilities/getPathWithUrlBase'; import NotFound from 'Components/NotFound'; import Switch from 'Components/Router/Switch'; import ArtistIndexConnector from 'Artist/Index/ArtistIndexConnector'; -import AddNewArtistConnector from 'AddArtist/AddNewArtist/AddNewArtistConnector'; +import AddNewItemConnector from 'Search/AddNewItemConnector'; import ImportArtist from 'AddArtist/ImportArtist/ImportArtist'; import ArtistEditorConnector from 'Artist/Editor/ArtistEditorConnector'; import AlbumStudioConnector from 'AlbumStudio/AlbumStudioConnector'; @@ -72,8 +72,8 @@ function AppRoutes(props) { }
- Missing Albums, Singles, or Other Types? Modify or Create a New Metadata Profile! + Missing Albums, Singles, or Other Types? Modify or create a new + Metadata Profile + or manually + Search + for new items!
e.monitored); + const albumTypes = _.uniq(_.map(items, 'albumType')); return { isAlbumsFetching: isFetching, isAlbumsPopulated: isPopulated, albumsError: error, hasAlbums, - hasMonitoredAlbums + hasMonitoredAlbums, + albumTypes }; } ); @@ -65,20 +67,12 @@ function createMapStateToProps() { (state, { foreignArtistId }) => foreignArtistId, selectAlbums, selectTrackFiles, - (state) => state.settings.metadataProfiles, createAllArtistSelector(), createCommandsSelector(), - (foreignArtistId, albums, trackFiles, metadataProfiles, allArtists, commands) => { + (foreignArtistId, albums, trackFiles, allArtists, commands) => { const sortedArtist = _.orderBy(allArtists, 'sortName'); const artistIndex = _.findIndex(sortedArtist, { foreignArtistId }); const artist = sortedArtist[artistIndex]; - const metadataProfile = _.find(metadataProfiles.items, { id: artist.metadataProfileId }); - const albumTypes = _.reduce(metadataProfile.primaryAlbumTypes, (acc, primaryType) => { - if (primaryType.allowed) { - acc.push(primaryType.albumType.name); - } - return acc; - }, []); if (!artist) { return {}; @@ -89,7 +83,8 @@ function createMapStateToProps() { isAlbumsPopulated, albumsError, hasAlbums, - hasMonitoredAlbums + hasMonitoredAlbums, + albumTypes } = albums; const { diff --git a/frontend/src/Artist/Edit/EditArtistModalContent.css b/frontend/src/Artist/Edit/EditArtistModalContent.css index a2b6014df..fd7ddf093 100644 --- a/frontend/src/Artist/Edit/EditArtistModalContent.css +++ b/frontend/src/Artist/Edit/EditArtistModalContent.css @@ -3,3 +3,7 @@ margin-right: auto; } + +.labelIcon { + margin-left: 8px; +} diff --git a/frontend/src/Artist/Edit/EditArtistModalContent.js b/frontend/src/Artist/Edit/EditArtistModalContent.js index 73dd652e8..adeb61c1e 100644 --- a/frontend/src/Artist/Edit/EditArtistModalContent.js +++ b/frontend/src/Artist/Edit/EditArtistModalContent.js @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import { inputTypes, kinds } from 'Helpers/Props'; +import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props'; import Button from 'Components/Link/Button'; import SpinnerButton from 'Components/Link/SpinnerButton'; import ModalContent from 'Components/Modal/ModalContent'; @@ -11,7 +11,10 @@ import Form from 'Components/Form/Form'; import FormGroup from 'Components/Form/FormGroup'; import FormLabel from 'Components/Form/FormLabel'; import FormInputGroup from 'Components/Form/FormInputGroup'; +import Icon from 'Components/Icon'; +import Popover from 'Components/Tooltip/Popover'; import MoveArtistModal from 'Artist/MoveArtist/MoveArtistModal'; +import ArtistMetadataProfilePopoverContent from 'AddArtist/ArtistMetadataProfilePopoverContent'; import styles from './EditArtistModalContent.css'; class EditArtistModalContent extends Component { @@ -122,12 +125,28 @@ class EditArtistModalContent extends Component { { showMetadataProfile && - Metadata Profile + + Metadata Profile + + + } + title="Metadata Profile" + body={} + position={tooltipPositions.RIGHT} + /> + + diff --git a/frontend/src/Artist/NoArtist.js b/frontend/src/Artist/NoArtist.js index b869a8d58..1db9f41db 100644 --- a/frontend/src/Artist/NoArtist.js +++ b/frontend/src/Artist/NoArtist.js @@ -20,7 +20,7 @@ function NoArtist(props) { return (
- No artist found, to get started you'll want to add a new artist or import some existing ones. + No artist found, to get started you'll want to add a new artist or album or import some existing ones.
@@ -34,7 +34,7 @@ function NoArtist(props) {