mirror of
https://github.com/Readarr/Readarr.git
synced 2026-04-25 22:36:59 -04:00
New: Readarr 0.1
This commit is contained in:
@@ -1,160 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import LazyLoad from 'react-lazyload';
|
||||
|
||||
const logoPlaceholder = 'data:image/png;base64,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';
|
||||
|
||||
function findLogo(images) {
|
||||
return _.find(images, { coverType: 'logo' });
|
||||
}
|
||||
|
||||
function getLogoUrl(logo, size) {
|
||||
if (logo) {
|
||||
// Remove protocol
|
||||
let url = logo.url.replace(/^https?:/, '');
|
||||
url = url.replace('logo.jpg', `logo-${size}.jpg`);
|
||||
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
class ArtistLogo extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
const pixelRatio = Math.floor(window.devicePixelRatio);
|
||||
|
||||
const {
|
||||
images,
|
||||
size
|
||||
} = props;
|
||||
|
||||
const logo = findLogo(images);
|
||||
|
||||
this.state = {
|
||||
pixelRatio,
|
||||
logo,
|
||||
logoUrl: getLogoUrl(logo, pixelRatio * size),
|
||||
hasError: false,
|
||||
isLoaded: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
images,
|
||||
size
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
pixelRatio
|
||||
} = this.state;
|
||||
|
||||
const logo = findLogo(images);
|
||||
|
||||
if (logo && logo.url !== this.state.logo.url) {
|
||||
this.setState({
|
||||
logo,
|
||||
logoUrl: getLogoUrl(logo, pixelRatio * size),
|
||||
hasError: false,
|
||||
isLoaded: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onError = () => {
|
||||
this.setState({ hasError: true });
|
||||
}
|
||||
|
||||
onLoad = () => {
|
||||
this.setState({ isLoaded: true });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
style,
|
||||
size,
|
||||
lazy,
|
||||
overflow
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
logoUrl,
|
||||
hasError,
|
||||
isLoaded
|
||||
} = this.state;
|
||||
|
||||
if (hasError || !logoUrl) {
|
||||
return (
|
||||
<img
|
||||
className={className}
|
||||
style={style}
|
||||
src={logoPlaceholder}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (lazy) {
|
||||
return (
|
||||
<LazyLoad
|
||||
height={size}
|
||||
offset={100}
|
||||
overflow={overflow}
|
||||
placeholder={
|
||||
<img
|
||||
className={className}
|
||||
style={style}
|
||||
src={logoPlaceholder}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<img
|
||||
className={className}
|
||||
style={style}
|
||||
src={logoUrl}
|
||||
onError={this.onError}
|
||||
/>
|
||||
</LazyLoad>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<img
|
||||
className={className}
|
||||
style={style}
|
||||
src={isLoaded ? logoUrl : logoPlaceholder}
|
||||
onError={this.onError}
|
||||
onLoad={this.onLoad}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ArtistLogo.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
size: PropTypes.number.isRequired,
|
||||
lazy: PropTypes.bool.isRequired,
|
||||
overflow: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
ArtistLogo.defaultProps = {
|
||||
size: 250,
|
||||
lazy: true,
|
||||
overflow: false
|
||||
};
|
||||
|
||||
export default ArtistLogo;
|
||||
@@ -2,8 +2,8 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Link from 'Components/Link/Link';
|
||||
|
||||
function ArtistNameLink({ foreignArtistId, artistName }) {
|
||||
const link = `/artist/${foreignArtistId}`;
|
||||
function ArtistNameLink({ titleSlug, artistName }) {
|
||||
const link = `/author/${titleSlug}`;
|
||||
|
||||
return (
|
||||
<Link to={link}>
|
||||
@@ -13,7 +13,7 @@ function ArtistNameLink({ foreignArtistId, artistName }) {
|
||||
}
|
||||
|
||||
ArtistNameLink.propTypes = {
|
||||
foreignArtistId: PropTypes.string.isRequired,
|
||||
titleSlug: PropTypes.string.isRequired,
|
||||
artistName: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ArtistImage from './ArtistImage';
|
||||
|
||||
const posterPlaceholder = 'data:image/png;base64,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';
|
||||
const posterPlaceholder = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPcAAAD3AQMAAAD7QlAQAAAABlBMVEUnJychISEIs8G4AAAEFklEQVRYw+2YMdPOQBDH95KQDEVSMKOj1FFQJx9BQc0nkeuUvoLS+AQ6MQqlRu8xCjpUgsjK7iXz2t1LMsbo8i/ey5vfc3e7m7tk9+DQoUOHDpGe3bu7hS8BwJ117BoAOLfOb/Hf62s4EY1VNrcPVvjNua1WuJ/b8xqoeR3sqFkllx8+AYAra9PniDg1ydr07cT7FQMy6k7ycQMKgJr5F4BrhvI9ZA3xCDU8fJggs9gBXJ35acX8lil74CPmO5w1xhwoIMVFMQcqKCfynH3soLLuEfkB4O5TBArDPZlH05ZkYMxBigyJDEyseylHFjjK4CzPyS4IE3gTgIxuAyulHzbG/as0PYsifM24X8/TA19Vxn2efjagNwFoHE2/GDAKpm86HE2AfMrmLQbqADnI2bzFQPv8y7NlM7naORU+uid+62X4xJg0V6PC1+KfvvSghWMgnh0cVIArCO694Ib+qWR4HQ257F9oRxu+L2FpzK3h7D5vPwqA5k1OPOwA4iaAOYWnZM4XPhPYT3eWDXriX4sHROjpskF7cC2eBHfUdVjeDw6/4Uk9oHqEz18DH9se8IvgCdQDBS/oLUxcPcB24mnAv+jfXvCMOdwI9jNXDxiJp9w9DCd4Afgdz96fF5GGk3xSCFBHw+gF4PAz9SQCwE7K5UGculJHGuTdKPun+IYHrafAUPfPKJdP4OhL7ErDuf9jfnXn6Gu6+Kj654EPKQIG7iu5PMLacGPO7Qf0EOMvx3LhhRh/5l+GOsahnPkw4Mw7sXzLedzxV+DvscsMZ8X51W0Olp/+5P7qIPlLPMEWP+3z5G94rXinuen/RWzAbe6g7hVvRX/DO8FdjMPB9+O3yD5fwf1fc72+/jcfN/cHRPZPJva/7q/27z9zlPyVfL9Abrgv/oW/Nvyx5vL9rbl5f78R/I3iTnP7fRH83QjVDpfCb4Kr71uxz1FzkN9nxfX32XKVHyj+BfweV/mJkM5Pdnkpsc6PfK64BynDM8lTiU1+l+LPP2iLUJj8sj5z3uaXgMPZFDY/rQDHs/rLTRxMfkwx4mX4hPLjaza/TgIfI/l1xvl5y/wT5+dSCd8rmXf8W2/qgx5S5rRYvAMlri+Ic2MKME9FCdQT/wJ8Ga1vSnzE+Z3l06REJi7qI1VfOXw0xusrCPVZ+6aP12dFqO/qN6d4fZeF+rB804X6sInXl/lrT1vBFtAu1KcuCfWpi9e33VLfJjZAS33ckvlZpH4uedu2nOcWhleiPr9peLFT32fyfGD7fMGBlf/jfCLZOd8oIrw6q4/o2jogzlc2z2fAW8w2nwvd3eqp0YXxCcdiS1HzRC8fw2ezJjvHVtn2tPbhqnOzTgNp1/kdv6pV7ig4RQOruuDBCax1+94dOHTo0KFDk34DoJynpPus3GIAAAAASUVORK5CYII=';
|
||||
|
||||
function ArtistPoster(props) {
|
||||
return (
|
||||
|
||||
@@ -26,7 +26,7 @@ class DeleteArtistModalContentConnector extends Component {
|
||||
|
||||
onDeletePress = (deleteFiles, addImportListExclusion) => {
|
||||
this.props.deleteArtist({
|
||||
id: this.props.artistId,
|
||||
id: this.props.authorId,
|
||||
deleteFiles,
|
||||
addImportListExclusion
|
||||
});
|
||||
@@ -48,7 +48,7 @@ class DeleteArtistModalContentConnector extends Component {
|
||||
}
|
||||
|
||||
DeleteArtistModalContentConnector.propTypes = {
|
||||
artistId: PropTypes.number.isRequired,
|
||||
authorId: PropTypes.number.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired,
|
||||
deleteArtist: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -6,7 +6,6 @@ import { kinds, sizes } from 'Helpers/Props';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import Label from 'Components/Label';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
|
||||
import AlbumSearchCellConnector from 'Album/AlbumSearchCellConnector';
|
||||
import AlbumTitleLink from 'Album/AlbumTitleLink';
|
||||
import StarRating from 'Components/StarRating';
|
||||
@@ -67,19 +66,17 @@ class AlbumRow extends Component {
|
||||
render() {
|
||||
const {
|
||||
id,
|
||||
artistId,
|
||||
authorId,
|
||||
monitored,
|
||||
statistics,
|
||||
duration,
|
||||
releaseDate,
|
||||
mediumCount,
|
||||
secondaryTypes,
|
||||
title,
|
||||
position,
|
||||
ratings,
|
||||
disambiguation,
|
||||
isSaving,
|
||||
artistMonitored,
|
||||
foreignAlbumId,
|
||||
titleSlug,
|
||||
columns
|
||||
} = this.props;
|
||||
|
||||
@@ -125,7 +122,7 @@ class AlbumRow extends Component {
|
||||
className={styles.title}
|
||||
>
|
||||
<AlbumTitleLink
|
||||
foreignAlbumId={foreignAlbumId}
|
||||
titleSlug={titleSlug}
|
||||
title={title}
|
||||
disambiguation={disambiguation}
|
||||
/>
|
||||
@@ -133,42 +130,13 @@ class AlbumRow extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'mediumCount') {
|
||||
if (name === 'position') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
{
|
||||
mediumCount
|
||||
}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'secondaryTypes') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
{
|
||||
secondaryTypes
|
||||
}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'trackCount') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
{
|
||||
statistics.totalTrackCount
|
||||
}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'duration') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
{
|
||||
formatTimeSpan(duration)
|
||||
}
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.title}
|
||||
>
|
||||
{position || ''}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
@@ -218,8 +186,8 @@ class AlbumRow extends Component {
|
||||
return (
|
||||
<AlbumSearchCellConnector
|
||||
key={name}
|
||||
albumId={id}
|
||||
artistId={artistId}
|
||||
bookId={id}
|
||||
authorId={authorId}
|
||||
albumTitle={title}
|
||||
/>
|
||||
);
|
||||
@@ -234,21 +202,17 @@ class AlbumRow extends Component {
|
||||
|
||||
AlbumRow.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
artistId: PropTypes.number.isRequired,
|
||||
authorId: PropTypes.number.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
releaseDate: PropTypes.string.isRequired,
|
||||
mediumCount: PropTypes.number.isRequired,
|
||||
duration: PropTypes.number.isRequired,
|
||||
releaseDate: PropTypes.string,
|
||||
title: PropTypes.string.isRequired,
|
||||
position: PropTypes.string,
|
||||
ratings: PropTypes.object.isRequired,
|
||||
disambiguation: PropTypes.string,
|
||||
secondaryTypes: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
foreignAlbumId: PropTypes.string.isRequired,
|
||||
titleSlug: PropTypes.string.isRequired,
|
||||
isSaving: PropTypes.bool,
|
||||
unverifiedSceneNumbering: PropTypes.bool,
|
||||
artistMonitored: PropTypes.bool.isRequired,
|
||||
statistics: PropTypes.object.isRequired,
|
||||
mediaInfo: PropTypes.object,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onMonitorAlbumPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -11,7 +11,6 @@ function createMapStateToProps() {
|
||||
createTrackFileSelector(),
|
||||
(artist = {}, trackFile) => {
|
||||
return {
|
||||
foreignArtistId: artist.foreignArtistId,
|
||||
artistMonitored: artist.monitored,
|
||||
trackFilePath: trackFile ? trackFile.path : null
|
||||
};
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
.poster {
|
||||
flex-shrink: 0;
|
||||
margin-right: 35px;
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
@@ -96,6 +95,10 @@
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.filterIcon {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.artistNavigationButtons {
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -150,6 +153,31 @@
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.tabList {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-bottom: 1px solid $lightGray;
|
||||
}
|
||||
|
||||
.tab {
|
||||
position: relative;
|
||||
bottom: -1px;
|
||||
display: inline-block;
|
||||
padding: 6px 12px;
|
||||
border: 1px solid transparent;
|
||||
border-top: none;
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.selectedTab {
|
||||
border-bottom: 4px solid $linkColor;
|
||||
}
|
||||
|
||||
.tabContent {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointSmall) {
|
||||
.contentContainer {
|
||||
padding: 20px 0;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
|
||||
import TextTruncate from 'react-text-truncate';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
import selectAll from 'Utilities/Table/selectAll';
|
||||
@@ -21,21 +22,23 @@ import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import Popover from 'Components/Tooltip/Popover';
|
||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||
import TrackFileEditorModal from 'TrackFile/Editor/TrackFileEditorModal';
|
||||
import TrackFileEditorTable from 'TrackFile/Editor/TrackFileEditorTable';
|
||||
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';
|
||||
import DeleteArtistModal from 'Artist/Delete/DeleteArtistModal';
|
||||
import ArtistHistoryModal from 'Artist/History/ArtistHistoryModal';
|
||||
import ArtistHistoryTable from 'Artist/History/ArtistHistoryTable';
|
||||
import ArtistAlternateTitles from './ArtistAlternateTitles';
|
||||
import ArtistDetailsSeasonConnector from './ArtistDetailsSeasonConnector';
|
||||
import AuthorDetailsSeriesConnector from './AuthorDetailsSeriesConnector';
|
||||
import ArtistTagsConnector from './ArtistTagsConnector';
|
||||
import ArtistDetailsLinks from './ArtistDetailsLinks';
|
||||
import styles from './ArtistDetails.css';
|
||||
import InteractiveSearchTable from 'InteractiveSearch/InteractiveSearchTable';
|
||||
import InteractiveSearchFilterMenuConnector from 'InteractiveSearch/InteractiveSearchFilterMenuConnector';
|
||||
import InteractiveImportModal from '../../InteractiveImport/InteractiveImportModal';
|
||||
import ArtistInteractiveSearchModalConnector from 'Artist/Search/ArtistInteractiveSearchModalConnector';
|
||||
import Link from 'Components/Link/Link';
|
||||
|
||||
const defaultFontSize = parseInt(fonts.defaultFontSize);
|
||||
@@ -68,15 +71,13 @@ class ArtistDetails extends Component {
|
||||
this.state = {
|
||||
isOrganizeModalOpen: false,
|
||||
isRetagModalOpen: false,
|
||||
isManageTracksOpen: false,
|
||||
isEditArtistModalOpen: false,
|
||||
isDeleteArtistModalOpen: false,
|
||||
isArtistHistoryModalOpen: false,
|
||||
isInteractiveImportModalOpen: false,
|
||||
isInteractiveSearchModalOpen: false,
|
||||
allExpanded: false,
|
||||
allCollapsed: false,
|
||||
expandedState: {}
|
||||
expandedState: {},
|
||||
selectedTabIndex: 0
|
||||
};
|
||||
}
|
||||
|
||||
@@ -99,14 +100,6 @@ class ArtistDetails extends Component {
|
||||
this.setState({ isRetagModalOpen: false });
|
||||
}
|
||||
|
||||
onManageTracksPress = () => {
|
||||
this.setState({ isManageTracksOpen: true });
|
||||
}
|
||||
|
||||
onManageTracksModalClose = () => {
|
||||
this.setState({ isManageTracksOpen: false });
|
||||
}
|
||||
|
||||
onInteractiveImportPress = () => {
|
||||
this.setState({ isInteractiveImportModalOpen: true });
|
||||
}
|
||||
@@ -115,14 +108,6 @@ class ArtistDetails extends Component {
|
||||
this.setState({ isInteractiveImportModalOpen: false });
|
||||
}
|
||||
|
||||
onInteractiveSearchPress = () => {
|
||||
this.setState({ isInteractiveSearchModalOpen: true });
|
||||
}
|
||||
|
||||
onInteractiveSearchModalClose = () => {
|
||||
this.setState({ isInteractiveSearchModalOpen: false });
|
||||
}
|
||||
|
||||
onEditArtistPress = () => {
|
||||
this.setState({ isEditArtistModalOpen: true });
|
||||
}
|
||||
@@ -142,14 +127,6 @@ class ArtistDetails extends Component {
|
||||
this.setState({ isDeleteArtistModalOpen: false });
|
||||
}
|
||||
|
||||
onArtistHistoryPress = () => {
|
||||
this.setState({ isArtistHistoryModalOpen: true });
|
||||
}
|
||||
|
||||
onArtistHistoryModalClose = () => {
|
||||
this.setState({ isArtistHistoryModalOpen: false });
|
||||
}
|
||||
|
||||
onExpandAllPress = () => {
|
||||
const {
|
||||
allExpanded,
|
||||
@@ -159,7 +136,7 @@ class ArtistDetails extends Component {
|
||||
this.setState(getExpandedState(selectAll(expandedState, !allExpanded)));
|
||||
}
|
||||
|
||||
onExpandPress = (albumId, isExpanded) => {
|
||||
onExpandPress = (bookId, isExpanded) => {
|
||||
this.setState((state) => {
|
||||
const convertedState = {
|
||||
allSelected: state.allExpanded,
|
||||
@@ -167,7 +144,7 @@ class ArtistDetails extends Component {
|
||||
selectedState: state.expandedState
|
||||
};
|
||||
|
||||
const newState = toggleSelected(convertedState, [], albumId, isExpanded, false);
|
||||
const newState = toggleSelected(convertedState, [], bookId, isExpanded, false);
|
||||
|
||||
return getExpandedState(newState);
|
||||
});
|
||||
@@ -179,14 +156,12 @@ class ArtistDetails extends Component {
|
||||
render() {
|
||||
const {
|
||||
id,
|
||||
foreignArtistId,
|
||||
artistName,
|
||||
ratings,
|
||||
path,
|
||||
statistics,
|
||||
qualityProfileId,
|
||||
monitored,
|
||||
albumTypes,
|
||||
status,
|
||||
overview,
|
||||
links,
|
||||
@@ -203,6 +178,8 @@ class ArtistDetails extends Component {
|
||||
trackFilesError,
|
||||
hasAlbums,
|
||||
hasMonitoredAlbums,
|
||||
hasSeries,
|
||||
series,
|
||||
hasTrackFiles,
|
||||
previousArtist,
|
||||
nextArtist,
|
||||
@@ -219,15 +196,13 @@ class ArtistDetails extends Component {
|
||||
const {
|
||||
isOrganizeModalOpen,
|
||||
isRetagModalOpen,
|
||||
isManageTracksOpen,
|
||||
isEditArtistModalOpen,
|
||||
isDeleteArtistModalOpen,
|
||||
isArtistHistoryModalOpen,
|
||||
isInteractiveImportModalOpen,
|
||||
isInteractiveSearchModalOpen,
|
||||
allExpanded,
|
||||
allCollapsed,
|
||||
expandedState
|
||||
expandedState,
|
||||
selectedTabIndex
|
||||
} = this.state;
|
||||
|
||||
const continuing = status === 'continuing';
|
||||
@@ -271,15 +246,6 @@ class ArtistDetails extends Component {
|
||||
onPress={onSearchPress}
|
||||
/>
|
||||
|
||||
<PageToolbarButton
|
||||
label="Interactive Search"
|
||||
iconName={icons.INTERACTIVE}
|
||||
isDisabled={!monitored || !hasMonitoredAlbums || !hasAlbums}
|
||||
isSpinning={isSearching}
|
||||
title={hasMonitoredAlbums ? undefined : 'No monitored albums for this artist'}
|
||||
onPress={this.onInteractiveSearchPress}
|
||||
/>
|
||||
|
||||
<PageToolbarSeparator />
|
||||
|
||||
<PageToolbarButton
|
||||
@@ -289,26 +255,12 @@ class ArtistDetails extends Component {
|
||||
onPress={this.onOrganizePress}
|
||||
/>
|
||||
|
||||
<PageToolbarButton
|
||||
label="Preview Retag"
|
||||
iconName={icons.RETAG}
|
||||
isDisabled={!hasTrackFiles}
|
||||
onPress={this.onRetagPress}
|
||||
/>
|
||||
|
||||
<PageToolbarButton
|
||||
label="Manage Tracks"
|
||||
iconName={icons.TRACK_FILE}
|
||||
isDisabled={!hasTrackFiles}
|
||||
onPress={this.onManageTracksPress}
|
||||
/>
|
||||
|
||||
<PageToolbarButton
|
||||
label="History"
|
||||
iconName={icons.HISTORY}
|
||||
isDisabled={!hasAlbums}
|
||||
onPress={this.onArtistHistoryPress}
|
||||
/>
|
||||
{/* <PageToolbarButton */}
|
||||
{/* label="Preview Retag" */}
|
||||
{/* iconName={icons.RETAG} */}
|
||||
{/* isDisabled={!hasTrackFiles} */}
|
||||
{/* onPress={this.onRetagPress} */}
|
||||
{/* /> */}
|
||||
|
||||
<PageToolbarButton
|
||||
label="Manual Import"
|
||||
@@ -400,7 +352,7 @@ class ArtistDetails extends Component {
|
||||
name={icons.ARROW_LEFT}
|
||||
size={30}
|
||||
title={`Go to ${previousArtist.artistName}`}
|
||||
to={`/artist/${previousArtist.foreignArtistId}`}
|
||||
to={`/author/${previousArtist.titleSlug}`}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
@@ -416,7 +368,7 @@ class ArtistDetails extends Component {
|
||||
name={icons.ARROW_RIGHT}
|
||||
size={30}
|
||||
title={`Go to ${nextArtist.artistName}`}
|
||||
to={`/artist/${nextArtist.foreignArtistId}`}
|
||||
to={`/author/${nextArtist.titleSlug}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -528,7 +480,6 @@ class ArtistDetails extends Component {
|
||||
}
|
||||
tooltip={
|
||||
<ArtistDetailsLinks
|
||||
foreignArtistId={foreignArtistId}
|
||||
links={links}
|
||||
/>
|
||||
}
|
||||
@@ -554,7 +505,7 @@ class ArtistDetails extends Component {
|
||||
</span>
|
||||
</Label>
|
||||
}
|
||||
tooltip={<ArtistTagsConnector artistId={id} />}
|
||||
tooltip={<ArtistTagsConnector authorId={id} />}
|
||||
kind={kinds.INVERSE}
|
||||
position={tooltipPositions.BOTTOM}
|
||||
/>
|
||||
@@ -564,7 +515,7 @@ class ArtistDetails extends Component {
|
||||
<div className={styles.overview}>
|
||||
<TextTruncate
|
||||
line={Math.floor(125 / (defaultFontSize * lineHeight))}
|
||||
text={overview}
|
||||
text={overview.replace(/<[^>]*>?/gm, '')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -588,30 +539,110 @@ class ArtistDetails extends Component {
|
||||
}
|
||||
|
||||
{
|
||||
isPopulated && !!albumTypes.length &&
|
||||
<div>
|
||||
{
|
||||
albumTypes.slice(0).map((albumType) => {
|
||||
return (
|
||||
<ArtistDetailsSeasonConnector
|
||||
key={albumType}
|
||||
artistId={id}
|
||||
name={albumType}
|
||||
label={albumType}
|
||||
{...albumType}
|
||||
isExpanded={expandedState[albumType]}
|
||||
onExpandPress={this.onExpandPress}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
isPopulated &&
|
||||
<Tabs selectedIndex={this.state.tabIndex} onSelect={(tabIndex) => this.setState({ selectedTabIndex: tabIndex })}>
|
||||
<TabList
|
||||
className={styles.tabList}
|
||||
>
|
||||
<Tab
|
||||
className={styles.tab}
|
||||
selectedClassName={styles.selectedTab}
|
||||
>
|
||||
Books
|
||||
</Tab>
|
||||
|
||||
<Tab
|
||||
className={styles.tab}
|
||||
selectedClassName={styles.selectedTab}
|
||||
>
|
||||
Series
|
||||
</Tab>
|
||||
|
||||
<Tab
|
||||
className={styles.tab}
|
||||
selectedClassName={styles.selectedTab}
|
||||
>
|
||||
History
|
||||
</Tab>
|
||||
|
||||
<Tab
|
||||
className={styles.tab}
|
||||
selectedClassName={styles.selectedTab}
|
||||
>
|
||||
Search
|
||||
</Tab>
|
||||
|
||||
<Tab
|
||||
className={styles.tab}
|
||||
selectedClassName={styles.selectedTab}
|
||||
>
|
||||
Files
|
||||
</Tab>
|
||||
|
||||
{
|
||||
selectedTabIndex === 3 &&
|
||||
<div className={styles.filterIcon}>
|
||||
<InteractiveSearchFilterMenuConnector
|
||||
type="artist"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</TabList>
|
||||
|
||||
<TabPanel>
|
||||
<ArtistDetailsSeasonConnector
|
||||
authorId={id}
|
||||
isExpanded={true}
|
||||
onExpandPress={this.onExpandPress}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
{
|
||||
isPopulated && hasSeries &&
|
||||
<div>
|
||||
{
|
||||
series.map((item) => {
|
||||
return (
|
||||
<AuthorDetailsSeriesConnector
|
||||
key={item.id}
|
||||
seriesId={item.id}
|
||||
authorId={id}
|
||||
isExpanded={expandedState[item.id]}
|
||||
onExpandPress={this.onExpandPress}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
<ArtistHistoryTable
|
||||
authorId={id}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
<InteractiveSearchTable
|
||||
type="artist"
|
||||
authorId={id}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
<TrackFileEditorTable
|
||||
authorId={id}
|
||||
/>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
<div className={styles.metadataMessage}>
|
||||
Missing Albums, Singles, or Other Types? Modify or create a new
|
||||
Missing or too many books? Modify or create a new
|
||||
<Link to='/settings/profiles'> Metadata Profile </Link>
|
||||
or manually
|
||||
<Link to={`/add/search?term=${encodeURIComponent(artistName)}`}> Search </Link>
|
||||
@@ -620,38 +651,26 @@ class ArtistDetails extends Component {
|
||||
|
||||
<OrganizePreviewModalConnector
|
||||
isOpen={isOrganizeModalOpen}
|
||||
artistId={id}
|
||||
authorId={id}
|
||||
onModalClose={this.onOrganizeModalClose}
|
||||
/>
|
||||
|
||||
<RetagPreviewModalConnector
|
||||
isOpen={isRetagModalOpen}
|
||||
artistId={id}
|
||||
authorId={id}
|
||||
onModalClose={this.onRetagModalClose}
|
||||
/>
|
||||
|
||||
<TrackFileEditorModal
|
||||
isOpen={isManageTracksOpen}
|
||||
artistId={id}
|
||||
onModalClose={this.onManageTracksModalClose}
|
||||
/>
|
||||
|
||||
<ArtistHistoryModal
|
||||
isOpen={isArtistHistoryModalOpen}
|
||||
artistId={id}
|
||||
onModalClose={this.onArtistHistoryModalClose}
|
||||
/>
|
||||
|
||||
<EditArtistModalConnector
|
||||
isOpen={isEditArtistModalOpen}
|
||||
artistId={id}
|
||||
authorId={id}
|
||||
onModalClose={this.onEditArtistModalClose}
|
||||
onDeleteArtistPress={this.onDeleteArtistPress}
|
||||
/>
|
||||
|
||||
<DeleteArtistModal
|
||||
isOpen={isDeleteArtistModalOpen}
|
||||
artistId={id}
|
||||
authorId={id}
|
||||
onModalClose={this.onDeleteArtistModalClose}
|
||||
/>
|
||||
|
||||
@@ -663,12 +682,6 @@ class ArtistDetails extends Component {
|
||||
showImportMode={false}
|
||||
onModalClose={this.onInteractiveImportModalClose}
|
||||
/>
|
||||
|
||||
<ArtistInteractiveSearchModalConnector
|
||||
isOpen={isInteractiveSearchModalOpen}
|
||||
artistId={id}
|
||||
onModalClose={this.onInteractiveSearchModalClose}
|
||||
/>
|
||||
</PageContentBodyConnector>
|
||||
</PageContent>
|
||||
);
|
||||
@@ -677,7 +690,6 @@ class ArtistDetails extends Component {
|
||||
|
||||
ArtistDetails.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
foreignArtistId: PropTypes.string.isRequired,
|
||||
artistName: PropTypes.string.isRequired,
|
||||
ratings: PropTypes.object.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
@@ -685,7 +697,6 @@ ArtistDetails.propTypes = {
|
||||
qualityProfileId: PropTypes.number.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
artistType: PropTypes.string,
|
||||
albumTypes: PropTypes.arrayOf(PropTypes.string),
|
||||
status: PropTypes.string.isRequired,
|
||||
overview: PropTypes.string.isRequired,
|
||||
links: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
@@ -701,6 +712,8 @@ ArtistDetails.propTypes = {
|
||||
trackFilesError: PropTypes.object,
|
||||
hasAlbums: PropTypes.bool.isRequired,
|
||||
hasMonitoredAlbums: PropTypes.bool.isRequired,
|
||||
hasSeries: PropTypes.bool.isRequired,
|
||||
series: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
hasTrackFiles: PropTypes.bool.isRequired,
|
||||
previousArtist: PropTypes.object.isRequired,
|
||||
nextArtist: PropTypes.object.isRequired,
|
||||
|
||||
@@ -6,12 +6,15 @@ import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { findCommand, isCommandExecuting } from 'Utilities/Command';
|
||||
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
||||
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
||||
import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
|
||||
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
|
||||
import { fetchAlbums, clearAlbums } from 'Store/Actions/albumActions';
|
||||
import { fetchSeries, clearSeries } from 'Store/Actions/seriesActions';
|
||||
import { fetchTrackFiles, clearTrackFiles } from 'Store/Actions/trackFileActions';
|
||||
import { toggleArtistMonitored } from 'Store/Actions/artistActions';
|
||||
import { fetchQueueDetails, clearQueueDetails } from 'Store/Actions/queueActions';
|
||||
import { clearReleases, cancelFetchReleases } from 'Store/Actions/releaseActions';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import * as commandNames from 'Commands/commandNames';
|
||||
import ArtistDetails from './ArtistDetails';
|
||||
@@ -28,15 +31,36 @@ const selectAlbums = createSelector(
|
||||
|
||||
const hasAlbums = !!items.length;
|
||||
const hasMonitoredAlbums = items.some((e) => e.monitored);
|
||||
const albumTypes = _.uniq(_.map(items, 'albumType'));
|
||||
|
||||
return {
|
||||
isAlbumsFetching: isFetching,
|
||||
isAlbumsPopulated: isPopulated,
|
||||
albumsError: error,
|
||||
hasAlbums,
|
||||
hasMonitoredAlbums,
|
||||
albumTypes
|
||||
hasMonitoredAlbums
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const selectSeries = createSelector(
|
||||
createSortedSectionSelector('series', (a, b) => a.title.localeCompare(b.title)),
|
||||
(state) => state.series,
|
||||
(series) => {
|
||||
const {
|
||||
items,
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error
|
||||
} = series;
|
||||
|
||||
const hasSeries = !!items.length;
|
||||
|
||||
return {
|
||||
isSeriesFetching: isFetching,
|
||||
isSeriesPopulated: isPopulated,
|
||||
seriesError: error,
|
||||
hasSeries,
|
||||
series: series.items
|
||||
};
|
||||
}
|
||||
);
|
||||
@@ -64,14 +88,15 @@ const selectTrackFiles = createSelector(
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state, { foreignArtistId }) => foreignArtistId,
|
||||
(state, { titleSlug }) => titleSlug,
|
||||
selectAlbums,
|
||||
selectSeries,
|
||||
selectTrackFiles,
|
||||
createAllArtistSelector(),
|
||||
createCommandsSelector(),
|
||||
(foreignArtistId, albums, trackFiles, allArtists, commands) => {
|
||||
(titleSlug, albums, series, trackFiles, allArtists, commands) => {
|
||||
const sortedArtist = _.orderBy(allArtists, 'sortName');
|
||||
const artistIndex = _.findIndex(sortedArtist, { foreignArtistId });
|
||||
const artistIndex = _.findIndex(sortedArtist, { titleSlug });
|
||||
const artist = sortedArtist[artistIndex];
|
||||
|
||||
if (!artist) {
|
||||
@@ -83,10 +108,17 @@ function createMapStateToProps() {
|
||||
isAlbumsPopulated,
|
||||
albumsError,
|
||||
hasAlbums,
|
||||
hasMonitoredAlbums,
|
||||
albumTypes
|
||||
hasMonitoredAlbums
|
||||
} = albums;
|
||||
|
||||
const {
|
||||
isSeriesFetching,
|
||||
isSeriesPopulated,
|
||||
seriesError,
|
||||
hasSeries,
|
||||
series: seriesItems
|
||||
} = series;
|
||||
|
||||
const {
|
||||
isTrackFilesFetching,
|
||||
isTrackFilesPopulated,
|
||||
@@ -94,28 +126,26 @@ function createMapStateToProps() {
|
||||
hasTrackFiles
|
||||
} = trackFiles;
|
||||
|
||||
const sortedAlbumTypes = _.orderBy(albumTypes);
|
||||
|
||||
const previousArtist = sortedArtist[artistIndex - 1] || _.last(sortedArtist);
|
||||
const nextArtist = sortedArtist[artistIndex + 1] || _.first(sortedArtist);
|
||||
const isArtistRefreshing = isCommandExecuting(findCommand(commands, { name: commandNames.REFRESH_ARTIST, artistId: artist.id }));
|
||||
const isArtistRefreshing = isCommandExecuting(findCommand(commands, { name: commandNames.REFRESH_ARTIST, authorId: artist.id }));
|
||||
const artistRefreshingCommand = findCommand(commands, { name: commandNames.REFRESH_ARTIST });
|
||||
const allArtistRefreshing = (
|
||||
isCommandExecuting(artistRefreshingCommand) &&
|
||||
!artistRefreshingCommand.body.artistId
|
||||
!artistRefreshingCommand.body.authorId
|
||||
);
|
||||
const isRefreshing = isArtistRefreshing || allArtistRefreshing;
|
||||
const isSearching = isCommandExecuting(findCommand(commands, { name: commandNames.ARTIST_SEARCH, artistId: artist.id }));
|
||||
const isRenamingFiles = isCommandExecuting(findCommand(commands, { name: commandNames.RENAME_FILES, artistId: artist.id }));
|
||||
const isSearching = isCommandExecuting(findCommand(commands, { name: commandNames.ARTIST_SEARCH, authorId: artist.id }));
|
||||
const isRenamingFiles = isCommandExecuting(findCommand(commands, { name: commandNames.RENAME_FILES, authorId: artist.id }));
|
||||
|
||||
const isRenamingArtistCommand = findCommand(commands, { name: commandNames.RENAME_ARTIST });
|
||||
const isRenamingArtist = (
|
||||
isCommandExecuting(isRenamingArtistCommand) &&
|
||||
isRenamingArtistCommand.body.artistIds.indexOf(artist.id) > -1
|
||||
isRenamingArtistCommand.body.authorIds.indexOf(artist.id) > -1
|
||||
);
|
||||
|
||||
const isFetching = isAlbumsFetching || isTrackFilesFetching;
|
||||
const isPopulated = isAlbumsPopulated && isTrackFilesPopulated;
|
||||
const isFetching = isAlbumsFetching || isSeriesFetching || isTrackFilesFetching;
|
||||
const isPopulated = isAlbumsPopulated && isSeriesPopulated && isTrackFilesPopulated;
|
||||
|
||||
const alternateTitles = _.reduce(artist.alternateTitles, (acc, alternateTitle) => {
|
||||
if ((alternateTitle.seasonNumber === -1 || alternateTitle.seasonNumber === undefined) &&
|
||||
@@ -128,7 +158,6 @@ function createMapStateToProps() {
|
||||
|
||||
return {
|
||||
...artist,
|
||||
albumTypes: sortedAlbumTypes,
|
||||
alternateTitles,
|
||||
isArtistRefreshing,
|
||||
allArtistRefreshing,
|
||||
@@ -139,9 +168,12 @@ function createMapStateToProps() {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
albumsError,
|
||||
seriesError,
|
||||
trackFilesError,
|
||||
hasAlbums,
|
||||
hasMonitoredAlbums,
|
||||
hasSeries,
|
||||
series: seriesItems,
|
||||
hasTrackFiles,
|
||||
previousArtist,
|
||||
nextArtist
|
||||
@@ -153,11 +185,15 @@ function createMapStateToProps() {
|
||||
const mapDispatchToProps = {
|
||||
fetchAlbums,
|
||||
clearAlbums,
|
||||
fetchSeries,
|
||||
clearSeries,
|
||||
fetchTrackFiles,
|
||||
clearTrackFiles,
|
||||
toggleArtistMonitored,
|
||||
fetchQueueDetails,
|
||||
clearQueueDetails,
|
||||
clearReleases,
|
||||
cancelFetchReleases,
|
||||
executeCommand
|
||||
};
|
||||
|
||||
@@ -207,17 +243,21 @@ class ArtistDetailsConnector extends Component {
|
||||
// Control
|
||||
|
||||
populate = () => {
|
||||
const artistId = this.props.id;
|
||||
const authorId = this.props.id;
|
||||
|
||||
this.props.fetchAlbums({ artistId });
|
||||
this.props.fetchTrackFiles({ artistId });
|
||||
this.props.fetchQueueDetails({ artistId });
|
||||
this.props.fetchAlbums({ authorId });
|
||||
this.props.fetchSeries({ authorId });
|
||||
this.props.fetchTrackFiles({ authorId });
|
||||
this.props.fetchQueueDetails({ authorId });
|
||||
}
|
||||
|
||||
unpopulate = () => {
|
||||
this.props.cancelFetchReleases();
|
||||
this.props.clearAlbums();
|
||||
this.props.clearSeries();
|
||||
this.props.clearTrackFiles();
|
||||
this.props.clearQueueDetails();
|
||||
this.props.clearReleases();
|
||||
}
|
||||
|
||||
//
|
||||
@@ -225,7 +265,7 @@ class ArtistDetailsConnector extends Component {
|
||||
|
||||
onMonitorTogglePress = (monitored) => {
|
||||
this.props.toggleArtistMonitored({
|
||||
artistId: this.props.id,
|
||||
authorId: this.props.id,
|
||||
monitored
|
||||
});
|
||||
}
|
||||
@@ -233,14 +273,14 @@ class ArtistDetailsConnector extends Component {
|
||||
onRefreshPress = () => {
|
||||
this.props.executeCommand({
|
||||
name: commandNames.REFRESH_ARTIST,
|
||||
artistId: this.props.id
|
||||
authorId: this.props.id
|
||||
});
|
||||
}
|
||||
|
||||
onSearchPress = () => {
|
||||
this.props.executeCommand({
|
||||
name: commandNames.ARTIST_SEARCH,
|
||||
artistId: this.props.id
|
||||
authorId: this.props.id
|
||||
});
|
||||
}
|
||||
|
||||
@@ -261,7 +301,7 @@ class ArtistDetailsConnector extends Component {
|
||||
|
||||
ArtistDetailsConnector.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
foreignArtistId: PropTypes.string.isRequired,
|
||||
titleSlug: PropTypes.string.isRequired,
|
||||
isArtistRefreshing: PropTypes.bool.isRequired,
|
||||
allArtistRefreshing: PropTypes.bool.isRequired,
|
||||
isRefreshing: PropTypes.bool.isRequired,
|
||||
@@ -269,11 +309,15 @@ ArtistDetailsConnector.propTypes = {
|
||||
isRenamingArtist: PropTypes.bool.isRequired,
|
||||
fetchAlbums: PropTypes.func.isRequired,
|
||||
clearAlbums: PropTypes.func.isRequired,
|
||||
fetchSeries: PropTypes.func.isRequired,
|
||||
clearSeries: PropTypes.func.isRequired,
|
||||
fetchTrackFiles: PropTypes.func.isRequired,
|
||||
clearTrackFiles: PropTypes.func.isRequired,
|
||||
toggleArtistMonitored: PropTypes.func.isRequired,
|
||||
fetchQueueDetails: PropTypes.func.isRequired,
|
||||
clearQueueDetails: PropTypes.func.isRequired,
|
||||
clearReleases: PropTypes.func.isRequired,
|
||||
cancelFetchReleases: PropTypes.func.isRequired,
|
||||
executeCommand: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -7,26 +7,12 @@ import styles from './ArtistDetailsLinks.css';
|
||||
|
||||
function ArtistDetailsLinks(props) {
|
||||
const {
|
||||
foreignArtistId,
|
||||
links
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className={styles.links}>
|
||||
|
||||
<Link
|
||||
className={styles.link}
|
||||
to={`https://musicbrainz.org/artist/${foreignArtistId}`}
|
||||
>
|
||||
<Label
|
||||
className={styles.linkLabel}
|
||||
kind={kinds.INFO}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
Musicbrainz
|
||||
</Label>
|
||||
</Link>
|
||||
|
||||
{links.map((link, index) => {
|
||||
return (
|
||||
<span key={index}>
|
||||
@@ -56,7 +42,6 @@ function ArtistDetailsLinks(props) {
|
||||
}
|
||||
|
||||
ArtistDetailsLinks.propTypes = {
|
||||
foreignArtistId: PropTypes.string.isRequired,
|
||||
links: PropTypes.arrayOf(PropTypes.object).isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ function createMapStateToProps() {
|
||||
(state, { match }) => match,
|
||||
(state) => state.artist,
|
||||
(match, artist) => {
|
||||
const foreignArtistId = match.params.foreignArtistId;
|
||||
const titleSlug = match.params.titleSlug;
|
||||
const {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
@@ -25,13 +25,13 @@ function createMapStateToProps() {
|
||||
items
|
||||
} = artist;
|
||||
|
||||
const artistIndex = _.findIndex(items, { foreignArtistId });
|
||||
const artistIndex = _.findIndex(items, { titleSlug });
|
||||
|
||||
if (artistIndex > -1) {
|
||||
return {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
foreignArtistId
|
||||
titleSlug
|
||||
};
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ class ArtistDetailsPageConnector extends Component {
|
||||
// Lifecycle
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (!this.props.foreignArtistId) {
|
||||
if (!this.props.titleSlug) {
|
||||
this.props.push(`${window.Readarr.urlBase}/`);
|
||||
return;
|
||||
}
|
||||
@@ -65,7 +65,7 @@ class ArtistDetailsPageConnector extends Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
foreignArtistId,
|
||||
titleSlug,
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error
|
||||
@@ -84,33 +84,33 @@ class ArtistDetailsPageConnector extends Component {
|
||||
if (!isFetching && !!error) {
|
||||
return (
|
||||
<div className={styles.errorMessage}>
|
||||
{getErrorMessage(error, 'Failed to load artist from API')}
|
||||
{getErrorMessage(error, 'Failed to load author from API')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!foreignArtistId) {
|
||||
if (!titleSlug) {
|
||||
return (
|
||||
<NotFound
|
||||
message="Sorry, that artist cannot be found."
|
||||
message="Sorry, that author cannot be found."
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ArtistDetailsConnector
|
||||
foreignArtistId={foreignArtistId}
|
||||
titleSlug={titleSlug}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ArtistDetailsPageConnector.propTypes = {
|
||||
foreignArtistId: PropTypes.string,
|
||||
titleSlug: PropTypes.string,
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
match: PropTypes.shape({ params: PropTypes.shape({ foreignArtistId: PropTypes.string.isRequired }).isRequired }).isRequired,
|
||||
match: PropTypes.shape({ params: PropTypes.shape({ titleSlug: PropTypes.string.isRequired }).isRequired }).isRequired,
|
||||
push: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -2,14 +2,9 @@ import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import getToggledRange from 'Utilities/Table/getToggledRange';
|
||||
import { icons, sortDirections } from 'Helpers/Props';
|
||||
import Icon from 'Components/Icon';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import Link from 'Components/Link/Link';
|
||||
import { sortDirections } from 'Helpers/Props';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import TrackFileEditorModal from 'TrackFile/Editor/TrackFileEditorModal';
|
||||
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
|
||||
import AlbumRowConnector from './AlbumRowConnector';
|
||||
import styles from './ArtistDetailsSeason.css';
|
||||
|
||||
@@ -22,92 +17,29 @@ class ArtistDetailsSeason extends Component {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isOrganizeModalOpen: false,
|
||||
isManageTracksOpen: false,
|
||||
lastToggledAlbum: null
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._expandByDefault();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
artistId
|
||||
} = this.props;
|
||||
|
||||
if (prevProps.artistId !== artistId) {
|
||||
this._expandByDefault();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
_expandByDefault() {
|
||||
const {
|
||||
name,
|
||||
onExpandPress,
|
||||
items,
|
||||
uiSettings
|
||||
} = this.props;
|
||||
|
||||
const expand = _.some(items, (item) =>
|
||||
((item.albumType === 'Album') && uiSettings.expandAlbumByDefault) ||
|
||||
((item.albumType === 'Single') && uiSettings.expandSingleByDefault) ||
|
||||
((item.albumType === 'EP') && uiSettings.expandEPByDefault) ||
|
||||
((item.albumType === 'Broadcast') && uiSettings.expandBroadcastByDefault) ||
|
||||
((item.albumType === 'Other') && uiSettings.expandOtherByDefault));
|
||||
|
||||
onExpandPress(name, expand);
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onOrganizePress = () => {
|
||||
this.setState({ isOrganizeModalOpen: true });
|
||||
}
|
||||
|
||||
onOrganizeModalClose = () => {
|
||||
this.setState({ isOrganizeModalOpen: false });
|
||||
}
|
||||
|
||||
onManageTracksPress = () => {
|
||||
this.setState({ isManageTracksOpen: true });
|
||||
}
|
||||
|
||||
onManageTracksModalClose = () => {
|
||||
this.setState({ isManageTracksOpen: false });
|
||||
}
|
||||
|
||||
onExpandPress = () => {
|
||||
const {
|
||||
name,
|
||||
isExpanded
|
||||
} = this.props;
|
||||
|
||||
this.props.onExpandPress(name, !isExpanded);
|
||||
}
|
||||
|
||||
onMonitorAlbumPress = (albumId, monitored, { shiftKey }) => {
|
||||
onMonitorAlbumPress = (bookId, monitored, { shiftKey }) => {
|
||||
const lastToggled = this.state.lastToggledAlbum;
|
||||
const albumIds = [albumId];
|
||||
const bookIds = [bookId];
|
||||
|
||||
if (shiftKey && lastToggled) {
|
||||
const { lower, upper } = getToggledRange(this.props.items, albumId, lastToggled);
|
||||
const { lower, upper } = getToggledRange(this.props.items, bookId, lastToggled);
|
||||
const items = this.props.items;
|
||||
|
||||
for (let i = lower; i < upper; i++) {
|
||||
albumIds.push(items[i].id);
|
||||
bookIds.push(items[i].id);
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({ lastToggledAlbum: albumId });
|
||||
this.setState({ lastToggledAlbum: bookId });
|
||||
|
||||
this.props.onMonitorAlbumPress(_.uniq(albumIds), monitored);
|
||||
this.props.onMonitorAlbumPress(_.uniq(bookIds), monitored);
|
||||
}
|
||||
|
||||
//
|
||||
@@ -115,134 +47,52 @@ class ArtistDetailsSeason extends Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
artistId,
|
||||
label,
|
||||
items,
|
||||
columns,
|
||||
isExpanded,
|
||||
sortKey,
|
||||
sortDirection,
|
||||
onSortPress,
|
||||
isSmallScreen,
|
||||
onTableOptionChange
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
isOrganizeModalOpen,
|
||||
isManageTracksOpen
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.albumType}
|
||||
>
|
||||
<Link
|
||||
className={styles.expandButton}
|
||||
onPress={this.onExpandPress}
|
||||
>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.left}>
|
||||
<div className={styles.albums}>
|
||||
<Table
|
||||
columns={columns}
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onSortPress={onSortPress}
|
||||
onTableOptionChange={onTableOptionChange}
|
||||
>
|
||||
<TableBody>
|
||||
{
|
||||
<div>
|
||||
<span className={styles.albumTypeLabel}>
|
||||
{label}
|
||||
</span>
|
||||
|
||||
<span className={styles.albumCount}>
|
||||
({items.length} Releases)
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
<Icon
|
||||
className={styles.expandButtonIcon}
|
||||
name={isExpanded ? icons.COLLAPSE : icons.EXPAND}
|
||||
title={isExpanded ? 'Hide albums' : 'Show albums'}
|
||||
size={24}
|
||||
/>
|
||||
|
||||
{
|
||||
!isSmallScreen &&
|
||||
<span> </span>
|
||||
}
|
||||
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<div>
|
||||
{
|
||||
isExpanded &&
|
||||
<div className={styles.albums}>
|
||||
{
|
||||
items.length ?
|
||||
<Table
|
||||
items.map((item) => {
|
||||
return (
|
||||
<AlbumRowConnector
|
||||
key={item.id}
|
||||
columns={columns}
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onSortPress={onSortPress}
|
||||
onTableOptionChange={onTableOptionChange}
|
||||
>
|
||||
<TableBody>
|
||||
{
|
||||
items.map((item) => {
|
||||
return (
|
||||
<AlbumRowConnector
|
||||
key={item.id}
|
||||
columns={columns}
|
||||
{...item}
|
||||
onMonitorAlbumPress={this.onMonitorAlbumPress}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</TableBody>
|
||||
</Table> :
|
||||
|
||||
<div className={styles.noAlbums}>
|
||||
No releases in this group
|
||||
</div>
|
||||
}
|
||||
<div className={styles.collapseButtonContainer}>
|
||||
<IconButton
|
||||
iconClassName={styles.collapseButtonIcon}
|
||||
name={icons.COLLAPSE}
|
||||
size={20}
|
||||
title="Hide albums"
|
||||
onPress={this.onExpandPress}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{...item}
|
||||
onMonitorAlbumPress={this.onMonitorAlbumPress}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<OrganizePreviewModalConnector
|
||||
isOpen={isOrganizeModalOpen}
|
||||
artistId={artistId}
|
||||
onModalClose={this.onOrganizeModalClose}
|
||||
/>
|
||||
|
||||
<TrackFileEditorModal
|
||||
isOpen={isManageTracksOpen}
|
||||
artistId={artistId}
|
||||
onModalClose={this.onManageTracksModalClose}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ArtistDetailsSeason.propTypes = {
|
||||
artistId: PropTypes.number.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
sortKey: PropTypes.string,
|
||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
isExpanded: PropTypes.bool,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
onTableOptionChange: PropTypes.func.isRequired,
|
||||
onExpandPress: PropTypes.func.isRequired,
|
||||
onSortPress: PropTypes.func.isRequired,
|
||||
|
||||
@@ -23,7 +23,7 @@ function createMapStateToProps() {
|
||||
createUISettingsSelector(),
|
||||
(label, albums, artist, commands, dimensions, uiSettings) => {
|
||||
|
||||
const albumsInGroup = _.filter(albums.items, { albumType: label });
|
||||
const albumsInGroup = albums.items;
|
||||
|
||||
let sortDir = 'asc';
|
||||
|
||||
@@ -66,9 +66,9 @@ class ArtistDetailsSeasonConnector extends Component {
|
||||
this.props.dispatchSetAlbumSort({ sortKey });
|
||||
}
|
||||
|
||||
onMonitorAlbumPress = (albumIds, monitored) => {
|
||||
onMonitorAlbumPress = (bookIds, monitored) => {
|
||||
this.props.toggleAlbumsMonitored({
|
||||
albumIds,
|
||||
bookIds,
|
||||
monitored
|
||||
});
|
||||
}
|
||||
@@ -89,7 +89,7 @@ class ArtistDetailsSeasonConnector extends Component {
|
||||
}
|
||||
|
||||
ArtistDetailsSeasonConnector.propTypes = {
|
||||
artistId: PropTypes.number.isRequired,
|
||||
authorId: PropTypes.number.isRequired,
|
||||
toggleAlbumsMonitored: PropTypes.func.isRequired,
|
||||
setAlbumsTableOption: PropTypes.func.isRequired,
|
||||
dispatchSetAlbumSort: PropTypes.func.isRequired,
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
.albumType {
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid $borderColor;
|
||||
border-radius: 4px;
|
||||
background-color: $white;
|
||||
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.albumTypeLabel {
|
||||
margin-right: 5px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.albumCount {
|
||||
color: #8895aa;
|
||||
font-style: italic;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.episodeCountTooltip {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.expandButton {
|
||||
composes: link from '~Components/Link/Link.css';
|
||||
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1 1 300px;
|
||||
}
|
||||
|
||||
.left,
|
||||
.actions {
|
||||
padding: 15px 10px;
|
||||
}
|
||||
|
||||
.actionsMenu {
|
||||
composes: menu from '~Components/Menu/Menu.css';
|
||||
|
||||
flex: 0 0 45px;
|
||||
}
|
||||
|
||||
.actionsMenuContent {
|
||||
composes: menuContent from '~Components/Menu/MenuContent.css';
|
||||
|
||||
white-space: nowrap;
|
||||
font-size: $defaultFontSize;
|
||||
}
|
||||
|
||||
.actionMenuIcon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.actionButton {
|
||||
composes: button from '~Components/Link/IconButton.css';
|
||||
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.albums {
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid $borderColor;
|
||||
}
|
||||
|
||||
.collapseButtonContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 15px;
|
||||
width: 100%;
|
||||
border-top: 1px solid $borderColor;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.collapseButtonIcon {
|
||||
margin-bottom: -4px;
|
||||
}
|
||||
|
||||
.expandButtonIcon {
|
||||
composes: actionButton;
|
||||
|
||||
margin-right: 15px;
|
||||
|
||||
/* position: absolute; */
|
||||
/* top: 50%; */
|
||||
/* left: 90%; */
|
||||
/* margin-top: -12px; */
|
||||
/* margin-left: -15px; */
|
||||
}
|
||||
|
||||
.noAlbums {
|
||||
margin-bottom: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointSmall) {
|
||||
.albumType {
|
||||
border-right: 0;
|
||||
border-left: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.expandButtonIcon {
|
||||
position: static;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import getToggledRange from 'Utilities/Table/getToggledRange';
|
||||
import { icons, sortDirections } from 'Helpers/Props';
|
||||
import Icon from 'Components/Icon';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import Link from 'Components/Link/Link';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import AlbumRowConnector from './AlbumRowConnector';
|
||||
import styles from './AuthorDetailsSeries.css';
|
||||
|
||||
class AuthorDetailsSeries extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isOrganizeModalOpen: false,
|
||||
isManageTracksOpen: false,
|
||||
lastToggledAlbum: null
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._expandByDefault();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
authorId
|
||||
} = this.props;
|
||||
|
||||
if (prevProps.authorId !== authorId) {
|
||||
this._expandByDefault();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
_expandByDefault() {
|
||||
const {
|
||||
id,
|
||||
onExpandPress
|
||||
} = this.props;
|
||||
|
||||
onExpandPress(id, true);
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onExpandPress = () => {
|
||||
const {
|
||||
id,
|
||||
isExpanded
|
||||
} = this.props;
|
||||
|
||||
this.props.onExpandPress(id, !isExpanded);
|
||||
}
|
||||
|
||||
onMonitorAlbumPress = (albumId, monitored, { shiftKey }) => {
|
||||
const lastToggled = this.state.lastToggledAlbum;
|
||||
const albumIds = [albumId];
|
||||
|
||||
if (shiftKey && lastToggled) {
|
||||
const { lower, upper } = getToggledRange(this.props.items, albumId, lastToggled);
|
||||
const items = this.props.items;
|
||||
|
||||
for (let i = lower; i < upper; i++) {
|
||||
albumIds.push(items[i].id);
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({ lastToggledAlbum: albumId });
|
||||
|
||||
this.props.onMonitorAlbumPress(_.uniq(albumIds), monitored);
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
label,
|
||||
items,
|
||||
positionMap,
|
||||
columns,
|
||||
isExpanded,
|
||||
sortKey,
|
||||
sortDirection,
|
||||
onSortPress,
|
||||
isSmallScreen,
|
||||
onTableOptionChange
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.albumType}
|
||||
>
|
||||
<Link
|
||||
className={styles.expandButton}
|
||||
onPress={this.onExpandPress}
|
||||
>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.left}>
|
||||
{
|
||||
<div>
|
||||
<span className={styles.albumTypeLabel}>
|
||||
{label}
|
||||
</span>
|
||||
|
||||
<span className={styles.albumCount}>
|
||||
({items.length} Books)
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
<Icon
|
||||
className={styles.expandButtonIcon}
|
||||
name={isExpanded ? icons.COLLAPSE : icons.EXPAND}
|
||||
title={isExpanded ? 'Hide books' : 'Show books'}
|
||||
size={24}
|
||||
/>
|
||||
|
||||
{
|
||||
!isSmallScreen &&
|
||||
<span> </span>
|
||||
}
|
||||
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<div>
|
||||
{
|
||||
isExpanded &&
|
||||
<div className={styles.albums}>
|
||||
<Table
|
||||
columns={columns}
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onSortPress={onSortPress}
|
||||
onTableOptionChange={onTableOptionChange}
|
||||
>
|
||||
<TableBody>
|
||||
{
|
||||
items.map((item) => {
|
||||
return (
|
||||
<AlbumRowConnector
|
||||
key={item.id}
|
||||
columns={columns}
|
||||
{...item}
|
||||
position={positionMap[item.id]}
|
||||
onMonitorAlbumPress={this.onMonitorAlbumPress}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
<div className={styles.collapseButtonContainer}>
|
||||
<IconButton
|
||||
iconClassName={styles.collapseButtonIcon}
|
||||
name={icons.COLLAPSE}
|
||||
size={20}
|
||||
title="Hide books"
|
||||
onPress={this.onExpandPress}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AuthorDetailsSeries.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
authorId: PropTypes.number.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
sortKey: PropTypes.string,
|
||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
positionMap: PropTypes.object.isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
isExpanded: PropTypes.bool,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
onTableOptionChange: PropTypes.func.isRequired,
|
||||
onExpandPress: PropTypes.func.isRequired,
|
||||
onSortPress: PropTypes.func.isRequired,
|
||||
onMonitorAlbumPress: PropTypes.func.isRequired,
|
||||
uiSettings: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default AuthorDetailsSeries;
|
||||
@@ -0,0 +1,121 @@
|
||||
/* eslint max-params: 0 */
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import createArtistSelector from 'Store/Selectors/createArtistSelector';
|
||||
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
|
||||
// import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
import { toggleAlbumsMonitored, setAlbumsTableOption } from 'Store/Actions/albumActions';
|
||||
import { setSeriesSort } from 'Store/Actions/seriesActions';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import AuthorDetailsSeries from './AuthorDetailsSeries';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state, { seriesId }) => seriesId,
|
||||
(state) => state.albums,
|
||||
createArtistSelector(),
|
||||
(state) => state.series,
|
||||
createCommandsSelector(),
|
||||
createDimensionsSelector(),
|
||||
createUISettingsSelector(),
|
||||
(seriesId, books, author, series, commands, dimensions, uiSettings) => {
|
||||
|
||||
const currentSeries = _.find(series.items, { id: seriesId });
|
||||
|
||||
const bookIds = currentSeries.links.map((x) => x.bookId);
|
||||
const positionMap = currentSeries.links.reduce((acc, curr) => {
|
||||
acc[curr.bookId] = curr.position;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const booksInSeries = _.filter(books.items, (book) => bookIds.includes(book.id));
|
||||
|
||||
let sortDir = 'asc';
|
||||
|
||||
if (series.sortDirection === 'descending') {
|
||||
sortDir = 'desc';
|
||||
}
|
||||
|
||||
let sortedBooks = [];
|
||||
if (series.sortKey === 'position') {
|
||||
sortedBooks = booksInSeries.sort((a, b) => {
|
||||
const apos = positionMap[a.id] || '';
|
||||
const bpos = positionMap[b.id] || '';
|
||||
return apos.localeCompare(bpos, undefined, { numeric: true, sensivity: 'base' });
|
||||
});
|
||||
} else {
|
||||
sortedBooks = _.orderBy(booksInSeries, series.sortKey, sortDir);
|
||||
}
|
||||
|
||||
return {
|
||||
id: currentSeries.id,
|
||||
label: currentSeries.title,
|
||||
items: sortedBooks,
|
||||
positionMap,
|
||||
columns: series.columns,
|
||||
sortKey: series.sortKey,
|
||||
sortDirection: series.sortDirection,
|
||||
artistMonitored: author.monitored,
|
||||
isSmallScreen: dimensions.isSmallScreen,
|
||||
uiSettings
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
toggleAlbumsMonitored,
|
||||
setAlbumsTableOption,
|
||||
dispatchSetSeriesSort: setSeriesSort,
|
||||
executeCommand
|
||||
};
|
||||
|
||||
class ArtistDetailsSeasonConnector extends Component {
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onTableOptionChange = (payload) => {
|
||||
this.props.setAlbumsTableOption(payload);
|
||||
}
|
||||
|
||||
onSortPress = (sortKey) => {
|
||||
this.props.dispatchSetSeriesSort({ sortKey });
|
||||
}
|
||||
|
||||
onMonitorAlbumPress = (bookIds, monitored) => {
|
||||
this.props.toggleAlbumsMonitored({
|
||||
bookIds,
|
||||
monitored
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<AuthorDetailsSeries
|
||||
{...this.props}
|
||||
onSortPress={this.onSortPress}
|
||||
onTableOptionChange={this.onTableOptionChange}
|
||||
onMonitorAlbumPress={this.onMonitorAlbumPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ArtistDetailsSeasonConnector.propTypes = {
|
||||
authorId: PropTypes.number.isRequired,
|
||||
toggleAlbumsMonitored: PropTypes.func.isRequired,
|
||||
setAlbumsTableOption: PropTypes.func.isRequired,
|
||||
dispatchSetSeriesSort: PropTypes.func.isRequired,
|
||||
executeCommand: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(ArtistDetailsSeasonConnector);
|
||||
@@ -72,7 +72,6 @@ class EditArtistModalContent extends Component {
|
||||
|
||||
const {
|
||||
monitored,
|
||||
albumFolder,
|
||||
qualityProfileId,
|
||||
metadataProfileId,
|
||||
path,
|
||||
@@ -99,18 +98,6 @@ class EditArtistModalContent extends Component {
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Use Album Folder</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="albumFolder"
|
||||
helpText="Sort tracks into album folders"
|
||||
{...albumFolder}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Quality Profile</FormLabel>
|
||||
|
||||
@@ -213,7 +200,7 @@ class EditArtistModalContent extends Component {
|
||||
}
|
||||
|
||||
EditArtistModalContent.propTypes = {
|
||||
artistId: PropTypes.number.isRequired,
|
||||
authorId: PropTypes.number.isRequired,
|
||||
artistName: PropTypes.string.isRequired,
|
||||
item: PropTypes.object.isRequired,
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
|
||||
@@ -87,7 +87,7 @@ class EditArtistModalContentConnector extends Component {
|
||||
|
||||
onSavePress = (moveFiles) => {
|
||||
this.props.dispatchSaveArtist({
|
||||
id: this.props.artistId,
|
||||
id: this.props.authorId,
|
||||
moveFiles
|
||||
});
|
||||
}
|
||||
@@ -108,7 +108,7 @@ class EditArtistModalContentConnector extends Component {
|
||||
}
|
||||
|
||||
EditArtistModalContentConnector.propTypes = {
|
||||
artistId: PropTypes.number,
|
||||
authorId: PropTypes.number,
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
saveError: PropTypes.object,
|
||||
dispatchSetArtistValue: PropTypes.func.isRequired,
|
||||
|
||||
@@ -45,12 +45,6 @@ function getColumns(showMetadataProfile) {
|
||||
isSortable: true,
|
||||
isVisible: showMetadataProfile
|
||||
},
|
||||
{
|
||||
name: 'albumFolder',
|
||||
label: 'Album Folder',
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'path',
|
||||
label: 'Path',
|
||||
@@ -122,7 +116,7 @@ class ArtistEditor extends Component {
|
||||
|
||||
onSaveSelected = (changes) => {
|
||||
this.props.onSaveSelected({
|
||||
artistIds: this.getSelectedIds(),
|
||||
authorIds: this.getSelectedIds(),
|
||||
...changes
|
||||
});
|
||||
}
|
||||
@@ -184,7 +178,7 @@ class ArtistEditor extends Component {
|
||||
columns
|
||||
} = this.state;
|
||||
|
||||
const selectedArtistIds = this.getSelectedIds();
|
||||
const selectedAuthorIds = this.getSelectedIds();
|
||||
|
||||
return (
|
||||
<PageContent title="Artist Editor">
|
||||
@@ -252,8 +246,8 @@ class ArtistEditor extends Component {
|
||||
</PageContentBodyConnector>
|
||||
|
||||
<ArtistEditorFooter
|
||||
artistIds={selectedArtistIds}
|
||||
selectedCount={selectedArtistIds.length}
|
||||
authorIds={selectedAuthorIds}
|
||||
selectedCount={selectedAuthorIds.length}
|
||||
isSaving={isSaving}
|
||||
saveError={saveError}
|
||||
isDeleting={isDeleting}
|
||||
@@ -268,13 +262,13 @@ class ArtistEditor extends Component {
|
||||
|
||||
<OrganizeArtistModal
|
||||
isOpen={this.state.isOrganizingArtistModalOpen}
|
||||
artistIds={selectedArtistIds}
|
||||
authorIds={selectedAuthorIds}
|
||||
onModalClose={this.onOrganizeArtistModalClose}
|
||||
/>
|
||||
|
||||
<RetagArtistModal
|
||||
isOpen={this.state.isRetaggingArtistModalOpen}
|
||||
artistIds={selectedArtistIds}
|
||||
authorIds={selectedAuthorIds}
|
||||
onModalClose={this.onRetagArtistModalClose}
|
||||
/>
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ class ArtistEditorFooter extends Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
artistIds,
|
||||
authorIds,
|
||||
selectedCount,
|
||||
isSaving,
|
||||
isDeleting,
|
||||
@@ -309,14 +309,14 @@ class ArtistEditorFooter extends Component {
|
||||
|
||||
<TagsModal
|
||||
isOpen={isTagsModalOpen}
|
||||
artistIds={artistIds}
|
||||
authorIds={authorIds}
|
||||
onApplyTagsPress={this.onApplyTagsPress}
|
||||
onModalClose={this.onTagsModalClose}
|
||||
/>
|
||||
|
||||
<DeleteArtistModal
|
||||
isOpen={isDeleteArtistModalOpen}
|
||||
artistIds={artistIds}
|
||||
authorIds={authorIds}
|
||||
onModalClose={this.onDeleteArtistModalClose}
|
||||
/>
|
||||
|
||||
@@ -333,7 +333,7 @@ class ArtistEditorFooter extends Component {
|
||||
}
|
||||
|
||||
ArtistEditorFooter.propTypes = {
|
||||
artistIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
authorIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
selectedCount: PropTypes.number.isRequired,
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
saveError: PropTypes.object,
|
||||
|
||||
@@ -2,7 +2,6 @@ import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import TagListConnector from 'Components/TagListConnector';
|
||||
import CheckInput from 'Components/Form/CheckInput';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
||||
@@ -27,13 +26,12 @@ class ArtistEditorRow extends Component {
|
||||
const {
|
||||
id,
|
||||
status,
|
||||
foreignArtistId,
|
||||
titleSlug,
|
||||
artistName,
|
||||
artistType,
|
||||
monitored,
|
||||
metadataProfile,
|
||||
qualityProfile,
|
||||
albumFolder,
|
||||
path,
|
||||
tags,
|
||||
columns,
|
||||
@@ -57,7 +55,7 @@ class ArtistEditorRow extends Component {
|
||||
|
||||
<TableRowCell className={styles.title}>
|
||||
<ArtistNameLink
|
||||
foreignArtistId={foreignArtistId}
|
||||
titleSlug={titleSlug}
|
||||
artistName={artistName}
|
||||
/>
|
||||
</TableRowCell>
|
||||
@@ -73,15 +71,6 @@ class ArtistEditorRow extends Component {
|
||||
</TableRowCell>
|
||||
}
|
||||
|
||||
<TableRowCell className={styles.albumFolder}>
|
||||
<CheckInput
|
||||
name="albumFolder"
|
||||
value={albumFolder}
|
||||
isDisabled={true}
|
||||
onChange={this.onAlbumFolderChange}
|
||||
/>
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell>
|
||||
{path}
|
||||
</TableRowCell>
|
||||
@@ -99,13 +88,12 @@ class ArtistEditorRow extends Component {
|
||||
ArtistEditorRow.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
foreignArtistId: PropTypes.string.isRequired,
|
||||
titleSlug: PropTypes.string.isRequired,
|
||||
artistName: PropTypes.string.isRequired,
|
||||
artistType: PropTypes.string,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
metadataProfile: PropTypes.object.isRequired,
|
||||
qualityProfile: PropTypes.object.isRequired,
|
||||
albumFolder: PropTypes.bool.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
|
||||
@@ -10,10 +10,10 @@ import RetagArtistModalContent from './RetagArtistModalContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state, { artistIds }) => artistIds,
|
||||
(state, { authorIds }) => authorIds,
|
||||
createAllArtistSelector(),
|
||||
(artistIds, allArtists) => {
|
||||
const artist = _.intersectionWith(allArtists, artistIds, (s, id) => {
|
||||
(authorIds, allArtists) => {
|
||||
const artist = _.intersectionWith(allArtists, authorIds, (s, id) => {
|
||||
return s.id === id;
|
||||
});
|
||||
|
||||
@@ -39,7 +39,7 @@ class RetagArtistModalContentConnector extends Component {
|
||||
onRetagArtistPress = () => {
|
||||
this.props.executeCommand({
|
||||
name: commandNames.RETAG_ARTIST,
|
||||
artistIds: this.props.artistIds
|
||||
authorIds: this.props.authorIds
|
||||
});
|
||||
|
||||
this.props.onModalClose(true);
|
||||
@@ -59,7 +59,7 @@ class RetagArtistModalContentConnector extends Component {
|
||||
}
|
||||
|
||||
RetagArtistModalContentConnector.propTypes = {
|
||||
artistIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
authorIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
onModalClose: PropTypes.func.isRequired,
|
||||
executeCommand: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -7,10 +7,10 @@ import DeleteArtistModalContent from './DeleteArtistModalContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state, { artistIds }) => artistIds,
|
||||
(state, { authorIds }) => authorIds,
|
||||
createAllArtistSelector(),
|
||||
(artistIds, allArtists) => {
|
||||
const selectedArtist = _.intersectionWith(allArtists, artistIds, (s, id) => {
|
||||
(authorIds, allArtists) => {
|
||||
const selectedArtist = _.intersectionWith(allArtists, authorIds, (s, id) => {
|
||||
return s.id === id;
|
||||
});
|
||||
|
||||
@@ -33,7 +33,7 @@ function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
onDeleteSelectedPress(deleteFiles) {
|
||||
dispatch(bulkDeleteArtist({
|
||||
artistIds: props.artistIds,
|
||||
authorIds: props.authorIds,
|
||||
deleteFiles
|
||||
}));
|
||||
|
||||
|
||||
@@ -10,10 +10,10 @@ import OrganizeArtistModalContent from './OrganizeArtistModalContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state, { artistIds }) => artistIds,
|
||||
(state, { authorIds }) => authorIds,
|
||||
createAllArtistSelector(),
|
||||
(artistIds, allArtists) => {
|
||||
const artist = _.intersectionWith(allArtists, artistIds, (s, id) => {
|
||||
(authorIds, allArtists) => {
|
||||
const artist = _.intersectionWith(allArtists, authorIds, (s, id) => {
|
||||
return s.id === id;
|
||||
});
|
||||
|
||||
@@ -39,7 +39,7 @@ class OrganizeArtistModalContentConnector extends Component {
|
||||
onOrganizeArtistPress = () => {
|
||||
this.props.executeCommand({
|
||||
name: commandNames.RENAME_ARTIST,
|
||||
artistIds: this.props.artistIds
|
||||
authorIds: this.props.authorIds
|
||||
});
|
||||
|
||||
this.props.onModalClose(true);
|
||||
@@ -59,7 +59,7 @@ class OrganizeArtistModalContentConnector extends Component {
|
||||
}
|
||||
|
||||
OrganizeArtistModalContentConnector.propTypes = {
|
||||
artistIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
authorIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
onModalClose: PropTypes.func.isRequired,
|
||||
executeCommand: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -7,11 +7,11 @@ import TagsModalContent from './TagsModalContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state, { artistIds }) => artistIds,
|
||||
(state, { authorIds }) => authorIds,
|
||||
createAllArtistSelector(),
|
||||
createTagsSelector(),
|
||||
(artistIds, allArtists, tagList) => {
|
||||
const artist = _.intersectionWith(allArtists, artistIds, (s, id) => {
|
||||
(authorIds, allArtists, tagList) => {
|
||||
const artist = _.intersectionWith(allArtists, authorIds, (s, id) => {
|
||||
return s.id === id;
|
||||
});
|
||||
|
||||
|
||||
+21
-16
@@ -3,7 +3,6 @@ import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { fetchArtistHistory, clearArtistHistory, artistHistoryMarkAsFailed } from 'Store/Actions/artistHistoryActions';
|
||||
import ArtistHistoryModalContent from './ArtistHistoryModalContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
@@ -20,20 +19,20 @@ const mapDispatchToProps = {
|
||||
artistHistoryMarkAsFailed
|
||||
};
|
||||
|
||||
class ArtistHistoryModalContentConnector extends Component {
|
||||
class ArtistHistoryContentConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
artistId,
|
||||
albumId
|
||||
authorId,
|
||||
bookId
|
||||
} = this.props;
|
||||
|
||||
this.props.fetchArtistHistory({
|
||||
artistId,
|
||||
albumId
|
||||
authorId,
|
||||
bookId
|
||||
});
|
||||
}
|
||||
|
||||
@@ -46,14 +45,14 @@ class ArtistHistoryModalContentConnector extends Component {
|
||||
|
||||
onMarkAsFailedPress = (historyId) => {
|
||||
const {
|
||||
artistId,
|
||||
albumId
|
||||
authorId,
|
||||
bookId
|
||||
} = this.props;
|
||||
|
||||
this.props.artistHistoryMarkAsFailed({
|
||||
historyId,
|
||||
artistId,
|
||||
albumId
|
||||
authorId,
|
||||
bookId
|
||||
});
|
||||
}
|
||||
|
||||
@@ -61,21 +60,27 @@ class ArtistHistoryModalContentConnector extends Component {
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
component: ViewComponent,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<ArtistHistoryModalContent
|
||||
{...this.props}
|
||||
<ViewComponent
|
||||
{...otherProps}
|
||||
onMarkAsFailedPress={this.onMarkAsFailedPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ArtistHistoryModalContentConnector.propTypes = {
|
||||
artistId: PropTypes.number.isRequired,
|
||||
albumId: PropTypes.number,
|
||||
ArtistHistoryContentConnector.propTypes = {
|
||||
component: PropTypes.elementType.isRequired,
|
||||
authorId: PropTypes.number.isRequired,
|
||||
bookId: PropTypes.number,
|
||||
fetchArtistHistory: PropTypes.func.isRequired,
|
||||
clearArtistHistory: PropTypes.func.isRequired,
|
||||
artistHistoryMarkAsFailed: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(ArtistHistoryModalContentConnector);
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(ArtistHistoryContentConnector);
|
||||
@@ -1,7 +1,8 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import ArtistHistoryModalContentConnector from './ArtistHistoryModalContentConnector';
|
||||
import ArtistHistoryContentConnector from './ArtistHistoryContentConnector';
|
||||
import ArtistHistoryModalContent from './ArtistHistoryModalContent';
|
||||
|
||||
function ArtistHistoryModal(props) {
|
||||
const {
|
||||
@@ -15,7 +16,8 @@ function ArtistHistoryModal(props) {
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<ArtistHistoryModalContentConnector
|
||||
<ArtistHistoryContentConnector
|
||||
component={ArtistHistoryModalContent}
|
||||
{...otherProps}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
|
||||
@@ -1,51 +1,11 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Button from 'Components/Link/Button';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
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 Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import ArtistHistoryRowConnector from './ArtistHistoryRowConnector';
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: 'eventType',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'album',
|
||||
label: 'Album',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'sourceTitle',
|
||||
label: 'Source Title',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'quality',
|
||||
label: 'Quality',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'date',
|
||||
label: 'Date',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'details',
|
||||
label: 'Details',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'actions',
|
||||
label: 'Actions',
|
||||
isVisible: true
|
||||
}
|
||||
];
|
||||
import ArtistHistoryTableContent from './ArtistHistoryTableContent';
|
||||
|
||||
class ArtistHistoryModalContent extends Component {
|
||||
|
||||
@@ -54,18 +14,9 @@ class ArtistHistoryModalContent extends Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
albumId,
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
items,
|
||||
onMarkAsFailedPress,
|
||||
onModalClose
|
||||
} = this.props;
|
||||
|
||||
const fullArtist = albumId == null;
|
||||
const hasItems = !!items.length;
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
@@ -73,40 +24,9 @@ class ArtistHistoryModalContent extends Component {
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
{
|
||||
isFetching &&
|
||||
<LoadingIndicator />
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>Unable to load history.</div>
|
||||
}
|
||||
|
||||
{
|
||||
isPopulated && !hasItems && !error &&
|
||||
<div>No history.</div>
|
||||
}
|
||||
|
||||
{
|
||||
isPopulated && hasItems && !error &&
|
||||
<Table columns={columns}>
|
||||
<TableBody>
|
||||
{
|
||||
items.map((item) => {
|
||||
return (
|
||||
<ArtistHistoryRowConnector
|
||||
key={item.id}
|
||||
fullArtist={fullArtist}
|
||||
{...item}
|
||||
onMarkAsFailedPress={onMarkAsFailedPress}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
}
|
||||
<ArtistHistoryTableContent
|
||||
{...this.props}
|
||||
/>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
@@ -120,12 +40,6 @@ class ArtistHistoryModalContent extends Component {
|
||||
}
|
||||
|
||||
ArtistHistoryModalContent.propTypes = {
|
||||
albumId: PropTypes.number,
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onMarkAsFailedPress: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import ArtistHistoryContentConnector from 'Artist/History/ArtistHistoryContentConnector';
|
||||
import ArtistHistoryTableContent from 'Artist/History/ArtistHistoryTableContent';
|
||||
|
||||
function ArtistHistoryTable(props) {
|
||||
const {
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<ArtistHistoryContentConnector
|
||||
component={ArtistHistoryTableContent}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
ArtistHistoryTable.propTypes = {
|
||||
};
|
||||
|
||||
export default ArtistHistoryTable;
|
||||
@@ -0,0 +1,113 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import ArtistHistoryRowConnector from './ArtistHistoryRowConnector';
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: 'eventType',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'album',
|
||||
label: 'Album',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'sourceTitle',
|
||||
label: 'Source Title',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'quality',
|
||||
label: 'Quality',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'date',
|
||||
label: 'Date',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'details',
|
||||
label: 'Details',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'actions',
|
||||
label: 'Actions',
|
||||
isVisible: true
|
||||
}
|
||||
];
|
||||
|
||||
class ArtistHistoryTableContent extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
bookId,
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
items,
|
||||
onMarkAsFailedPress
|
||||
} = this.props;
|
||||
|
||||
const fullArtist = bookId == null;
|
||||
const hasItems = !!items.length;
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
isFetching &&
|
||||
<LoadingIndicator />
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>Unable to load history.</div>
|
||||
}
|
||||
|
||||
{
|
||||
isPopulated && !hasItems && !error &&
|
||||
<div>No history.</div>
|
||||
}
|
||||
|
||||
{
|
||||
isPopulated && hasItems && !error &&
|
||||
<Table columns={columns}>
|
||||
<TableBody>
|
||||
{
|
||||
items.map((item) => {
|
||||
return (
|
||||
<ArtistHistoryRowConnector
|
||||
key={item.id}
|
||||
fullArtist={fullArtist}
|
||||
{...item}
|
||||
onMarkAsFailedPress={onMarkAsFailedPress}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ArtistHistoryTableContent.propTypes = {
|
||||
bookId: PropTypes.number,
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onMarkAsFailedPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default ArtistHistoryTableContent;
|
||||
@@ -60,7 +60,7 @@ class ArtistIndexFooter extends PureComponent {
|
||||
enableColorImpairedMode && 'colorImpaired'
|
||||
)}
|
||||
/>
|
||||
<div>Continuing (All tracks downloaded)</div>
|
||||
<div>Continuing (All books downloaded)</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.legendItem}>
|
||||
@@ -70,7 +70,7 @@ class ArtistIndexFooter extends PureComponent {
|
||||
enableColorImpairedMode && 'colorImpaired'
|
||||
)}
|
||||
/>
|
||||
<div>Ended (All tracks downloaded)</div>
|
||||
<div>Ended (All books downloaded)</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.legendItem}>
|
||||
@@ -80,7 +80,7 @@ class ArtistIndexFooter extends PureComponent {
|
||||
enableColorImpairedMode && 'colorImpaired'
|
||||
)}
|
||||
/>
|
||||
<div>Missing Tracks (Artist monitored)</div>
|
||||
<div>Missing Books (Author monitored)</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.legendItem}>
|
||||
@@ -90,14 +90,14 @@ class ArtistIndexFooter extends PureComponent {
|
||||
enableColorImpairedMode && 'colorImpaired'
|
||||
)}
|
||||
/>
|
||||
<div>Missing Tracks (Artist not monitored)</div>
|
||||
<div>Missing Books (Author not monitored)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.statistics}>
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
title="Artist"
|
||||
title="Authors"
|
||||
data={count}
|
||||
/>
|
||||
|
||||
@@ -126,7 +126,7 @@ class ArtistIndexFooter extends PureComponent {
|
||||
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
title="Tracks"
|
||||
title="Books"
|
||||
data={tracks}
|
||||
/>
|
||||
|
||||
|
||||
@@ -58,14 +58,14 @@ function createMapStateToProps() {
|
||||
const isRefreshingArtist = executingCommands.some((command) => {
|
||||
return (
|
||||
command.name === commandNames.REFRESH_ARTIST &&
|
||||
command.body.artistId === artist.id
|
||||
command.body.authorId === artist.id
|
||||
);
|
||||
});
|
||||
|
||||
const isSearchingArtist = executingCommands.some((command) => {
|
||||
return (
|
||||
command.name === commandNames.ARTIST_SEARCH &&
|
||||
command.body.artistId === artist.id
|
||||
command.body.authorId === artist.id
|
||||
);
|
||||
});
|
||||
|
||||
@@ -96,14 +96,14 @@ class ArtistIndexItemConnector extends Component {
|
||||
onRefreshArtistPress = () => {
|
||||
this.props.dispatchExecuteCommand({
|
||||
name: commandNames.REFRESH_ARTIST,
|
||||
artistId: this.props.id
|
||||
authorId: this.props.id
|
||||
});
|
||||
}
|
||||
|
||||
onSearchPress = () => {
|
||||
this.props.dispatchExecuteCommand({
|
||||
name: commandNames.ARTIST_SEARCH,
|
||||
artistId: this.props.id
|
||||
authorId: this.props.id
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ $hoverScale: 1.05;
|
||||
left: 10px;
|
||||
z-index: 3;
|
||||
border-radius: 4px;
|
||||
background-color: #216044;
|
||||
background-color: $themeLightColor;
|
||||
color: $white;
|
||||
font-size: $smallFontSize;
|
||||
opacity: 0;
|
||||
|
||||
@@ -58,7 +58,7 @@ class ArtistIndexBanner extends Component {
|
||||
artistName,
|
||||
monitored,
|
||||
status,
|
||||
foreignArtistId,
|
||||
titleSlug,
|
||||
nextAiring,
|
||||
statistics,
|
||||
images,
|
||||
@@ -93,7 +93,7 @@ class ArtistIndexBanner extends Component {
|
||||
isDeleteArtistModalOpen
|
||||
} = this.state;
|
||||
|
||||
const link = `/artist/${foreignArtistId}`;
|
||||
const link = `/author/${titleSlug}`;
|
||||
|
||||
const elementStyle = {
|
||||
width: `${bannerWidth}px`,
|
||||
@@ -216,14 +216,14 @@ class ArtistIndexBanner extends Component {
|
||||
|
||||
<EditArtistModalConnector
|
||||
isOpen={isEditArtistModalOpen}
|
||||
artistId={id}
|
||||
authorId={id}
|
||||
onModalClose={this.onEditArtistModalClose}
|
||||
onDeleteArtistPress={this.onDeleteArtistPress}
|
||||
/>
|
||||
|
||||
<DeleteArtistModal
|
||||
isOpen={isDeleteArtistModalOpen}
|
||||
artistId={id}
|
||||
authorId={id}
|
||||
onModalClose={this.onDeleteArtistModalClose}
|
||||
/>
|
||||
</div>
|
||||
@@ -237,7 +237,7 @@ ArtistIndexBanner.propTypes = {
|
||||
artistName: PropTypes.string.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
foreignArtistId: PropTypes.string.isRequired,
|
||||
titleSlug: PropTypes.string.isRequired,
|
||||
nextAiring: PropTypes.string,
|
||||
statistics: PropTypes.object.isRequired,
|
||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
|
||||
@@ -228,7 +228,7 @@ class ArtistIndexBanners extends Component {
|
||||
showRelativeDates={showRelativeDates}
|
||||
shortDateFormat={shortDateFormat}
|
||||
timeFormat={timeFormat}
|
||||
artistId={artist.id}
|
||||
authorId={artist.id}
|
||||
qualityProfileId={artist.qualityProfileId}
|
||||
metadataProfileId={artist.metadataProfileId}
|
||||
/>
|
||||
|
||||
@@ -75,7 +75,7 @@ class ArtistIndexOverview extends Component {
|
||||
overview,
|
||||
monitored,
|
||||
status,
|
||||
foreignArtistId,
|
||||
titleSlug,
|
||||
nextAiring,
|
||||
statistics,
|
||||
images,
|
||||
@@ -110,7 +110,7 @@ class ArtistIndexOverview extends Component {
|
||||
isDeleteArtistModalOpen
|
||||
} = this.state;
|
||||
|
||||
const link = `/artist/${foreignArtistId}`;
|
||||
const link = `/author/${titleSlug}`;
|
||||
|
||||
const elementStyle = {
|
||||
width: `${posterWidth}px`,
|
||||
@@ -228,14 +228,14 @@ class ArtistIndexOverview extends Component {
|
||||
|
||||
<EditArtistModalConnector
|
||||
isOpen={isEditArtistModalOpen}
|
||||
artistId={id}
|
||||
authorId={id}
|
||||
onModalClose={this.onEditArtistModalClose}
|
||||
onDeleteArtistPress={this.onDeleteArtistPress}
|
||||
/>
|
||||
|
||||
<DeleteArtistModal
|
||||
isOpen={isDeleteArtistModalOpen}
|
||||
artistId={id}
|
||||
authorId={id}
|
||||
onModalClose={this.onDeleteArtistModalClose}
|
||||
/>
|
||||
</div>
|
||||
@@ -249,7 +249,7 @@ ArtistIndexOverview.propTypes = {
|
||||
overview: PropTypes.string.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
foreignArtistId: PropTypes.string.isRequired,
|
||||
titleSlug: PropTypes.string.isRequired,
|
||||
nextAiring: PropTypes.string,
|
||||
statistics: PropTypes.object.isRequired,
|
||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
|
||||
@@ -172,7 +172,7 @@ class ArtistIndexOverviews extends Component {
|
||||
longDateFormat={longDateFormat}
|
||||
timeFormat={timeFormat}
|
||||
isSmallScreen={isSmallScreen}
|
||||
artistId={artist.id}
|
||||
authorId={artist.id}
|
||||
qualityProfileId={artist.qualityProfileId}
|
||||
metadataProfileId={artist.metadataProfileId}
|
||||
/>
|
||||
|
||||
@@ -81,7 +81,7 @@ $hoverScale: 1.05;
|
||||
left: 10px;
|
||||
z-index: 3;
|
||||
border-radius: 4px;
|
||||
background-color: #216044;
|
||||
background-color: $themeLightColor;
|
||||
color: $white;
|
||||
font-size: $smallFontSize;
|
||||
opacity: 0;
|
||||
|
||||
@@ -70,7 +70,7 @@ class ArtistIndexPoster extends Component {
|
||||
id,
|
||||
artistName,
|
||||
monitored,
|
||||
foreignArtistId,
|
||||
titleSlug,
|
||||
status,
|
||||
nextAiring,
|
||||
statistics,
|
||||
@@ -107,12 +107,13 @@ class ArtistIndexPoster extends Component {
|
||||
isDeleteArtistModalOpen
|
||||
} = this.state;
|
||||
|
||||
const link = `/artist/${foreignArtistId}`;
|
||||
const link = `/author/${titleSlug}`;
|
||||
|
||||
const elementStyle = {
|
||||
width: `${posterWidth}px`,
|
||||
height: `${posterHeight}px`
|
||||
};
|
||||
elementStyle['object-fit'] = 'contain';
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
@@ -239,14 +240,14 @@ class ArtistIndexPoster extends Component {
|
||||
|
||||
<EditArtistModalConnector
|
||||
isOpen={isEditArtistModalOpen}
|
||||
artistId={id}
|
||||
authorId={id}
|
||||
onModalClose={this.onEditArtistModalClose}
|
||||
onDeleteArtistPress={this.onDeleteArtistPress}
|
||||
/>
|
||||
|
||||
<DeleteArtistModal
|
||||
isOpen={isDeleteArtistModalOpen}
|
||||
artistId={id}
|
||||
authorId={id}
|
||||
onModalClose={this.onDeleteArtistModalClose}
|
||||
/>
|
||||
</div>
|
||||
@@ -260,7 +261,7 @@ ArtistIndexPoster.propTypes = {
|
||||
artistName: PropTypes.string.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
foreignArtistId: PropTypes.string.isRequired,
|
||||
titleSlug: PropTypes.string.isRequired,
|
||||
nextAiring: PropTypes.string,
|
||||
statistics: PropTypes.object.isRequired,
|
||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
|
||||
@@ -204,8 +204,8 @@ class ArtistIndexPosters extends Component {
|
||||
showQualityProfile
|
||||
} = posterOptions;
|
||||
|
||||
const artistIdx = rowIndex * columnCount + columnIndex;
|
||||
const artist = items[artistIdx];
|
||||
const authorIdx = rowIndex * columnCount + columnIndex;
|
||||
const artist = items[authorIdx];
|
||||
|
||||
if (!artist) {
|
||||
return null;
|
||||
@@ -229,7 +229,7 @@ class ArtistIndexPosters extends Component {
|
||||
showRelativeDates={showRelativeDates}
|
||||
shortDateFormat={shortDateFormat}
|
||||
timeFormat={timeFormat}
|
||||
artistId={artist.id}
|
||||
authorId={artist.id}
|
||||
qualityProfileId={artist.qualityProfileId}
|
||||
metadataProfileId={artist.metadataProfileId}
|
||||
/>
|
||||
|
||||
@@ -78,14 +78,14 @@ class ArtistIndexActionsCell extends Component {
|
||||
|
||||
<EditArtistModalConnector
|
||||
isOpen={isEditArtistModalOpen}
|
||||
artistId={id}
|
||||
authorId={id}
|
||||
onModalClose={this.onEditArtistModalClose}
|
||||
onDeleteArtistPress={this.onDeleteArtistPress}
|
||||
/>
|
||||
|
||||
<DeleteArtistModal
|
||||
isOpen={isDeleteArtistModalOpen}
|
||||
artistId={id}
|
||||
authorId={id}
|
||||
onModalClose={this.onDeleteArtistModalClose}
|
||||
/>
|
||||
</VirtualTableRowCell>
|
||||
|
||||
@@ -81,7 +81,7 @@ class ArtistIndexRow extends Component {
|
||||
monitored,
|
||||
status,
|
||||
artistName,
|
||||
foreignArtistId,
|
||||
titleSlug,
|
||||
artistType,
|
||||
qualityProfile,
|
||||
metadataProfile,
|
||||
@@ -157,7 +157,7 @@ class ArtistIndexRow extends Component {
|
||||
showBanners ?
|
||||
<Link
|
||||
className={styles.link}
|
||||
to={`/artist/${foreignArtistId}`}
|
||||
to={`/author/${titleSlug}`}
|
||||
>
|
||||
<ArtistBanner
|
||||
className={styles.bannerImage}
|
||||
@@ -177,7 +177,7 @@ class ArtistIndexRow extends Component {
|
||||
</Link> :
|
||||
|
||||
<ArtistNameLink
|
||||
foreignArtistId={foreignArtistId}
|
||||
titleSlug={titleSlug}
|
||||
artistName={artistName}
|
||||
/>
|
||||
}
|
||||
@@ -228,7 +228,7 @@ class ArtistIndexRow extends Component {
|
||||
<AlbumTitleLink
|
||||
title={nextAlbum.title}
|
||||
disambiguation={nextAlbum.disambiguation}
|
||||
foreignAlbumId={nextAlbum.foreignAlbumId}
|
||||
titleSlug={nextAlbum.titleSlug}
|
||||
/>
|
||||
</VirtualTableRowCell>
|
||||
);
|
||||
@@ -253,7 +253,7 @@ class ArtistIndexRow extends Component {
|
||||
<AlbumTitleLink
|
||||
title={lastAlbum.title}
|
||||
disambiguation={lastAlbum.disambiguation}
|
||||
foreignAlbumId={lastAlbum.foreignAlbumId}
|
||||
titleSlug={lastAlbum.titleSlug}
|
||||
/>
|
||||
</VirtualTableRowCell>
|
||||
);
|
||||
@@ -423,14 +423,14 @@ class ArtistIndexRow extends Component {
|
||||
|
||||
<EditArtistModalConnector
|
||||
isOpen={isEditArtistModalOpen}
|
||||
artistId={id}
|
||||
authorId={id}
|
||||
onModalClose={this.onEditArtistModalClose}
|
||||
onDeleteArtistPress={this.onDeleteArtistPress}
|
||||
/>
|
||||
|
||||
<DeleteArtistModal
|
||||
isOpen={isDeleteArtistModalOpen}
|
||||
artistId={id}
|
||||
authorId={id}
|
||||
onModalClose={this.onDeleteArtistModalClose}
|
||||
/>
|
||||
</>
|
||||
@@ -443,7 +443,7 @@ ArtistIndexRow.propTypes = {
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
artistName: PropTypes.string.isRequired,
|
||||
foreignArtistId: PropTypes.string.isRequired,
|
||||
titleSlug: PropTypes.string.isRequired,
|
||||
artistType: PropTypes.string,
|
||||
qualityProfile: PropTypes.object.isRequired,
|
||||
metadataProfile: PropTypes.object.isRequired,
|
||||
|
||||
@@ -62,7 +62,7 @@ class ArtistIndexTable extends Component {
|
||||
component={ArtistIndexRow}
|
||||
style={style}
|
||||
columns={columns}
|
||||
artistId={artist.id}
|
||||
authorId={artist.id}
|
||||
qualityProfileId={artist.qualityProfileId}
|
||||
metadataProfileId={artist.metadataProfileId}
|
||||
showBanners={showBanners}
|
||||
|
||||
@@ -11,7 +11,7 @@ function NoArtist(props) {
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.message}>
|
||||
All artists are hidden due to the applied filter.
|
||||
All authors are hidden due to the applied filter.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -20,7 +20,7 @@ function NoArtist(props) {
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.message}>
|
||||
No artists found, to get started you'll want to add a new artist or album or add an existing library location (Root Folder) and update.
|
||||
No authors found, to get started you'll want to add a new author or book or add an existing library location (Root Folder) and update.
|
||||
</div>
|
||||
|
||||
<div className={styles.buttonContainer}>
|
||||
@@ -37,7 +37,7 @@ function NoArtist(props) {
|
||||
to="/add/search"
|
||||
kind={kinds.PRIMARY}
|
||||
>
|
||||
Add New Artist
|
||||
Add New Author
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import ArtistInteractiveSearchModalContent from './ArtistInteractiveSearchModalContent';
|
||||
|
||||
function ArtistInteractiveSearchModal(props) {
|
||||
const {
|
||||
isOpen,
|
||||
artistId,
|
||||
onModalClose
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
closeOnBackgroundClick={false}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<ArtistInteractiveSearchModalContent
|
||||
artistId={artistId}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
ArtistInteractiveSearchModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
artistId: PropTypes.number.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default ArtistInteractiveSearchModal;
|
||||
@@ -1,15 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { cancelFetchReleases, clearReleases } from 'Store/Actions/releaseActions';
|
||||
import ArtistInteractiveSearchModal from './ArtistInteractiveSearchModal';
|
||||
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
onModalClose() {
|
||||
dispatch(cancelFetchReleases());
|
||||
dispatch(clearReleases());
|
||||
props.onModalClose();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(null, createMapDispatchToProps)(ArtistInteractiveSearchModal);
|
||||
@@ -1,45 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Button from 'Components/Link/Button';
|
||||
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 InteractiveSearchConnector from 'InteractiveSearch/InteractiveSearchConnector';
|
||||
|
||||
function ArtistInteractiveSearchModalContent(props) {
|
||||
const {
|
||||
artistId,
|
||||
onModalClose
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
Interactive Search
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<InteractiveSearchConnector
|
||||
type="artist"
|
||||
searchPayload={{
|
||||
artistId
|
||||
}}
|
||||
/>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={onModalClose}>
|
||||
Close
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
|
||||
ArtistInteractiveSearchModalContent.propTypes = {
|
||||
artistId: PropTypes.number.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default ArtistInteractiveSearchModalContent;
|
||||
Reference in New Issue
Block a user