New: Readarr 0.1
BIN
Logo/1024.png
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 39 KiB |
BIN
Logo/128.png
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 4.7 KiB |
BIN
Logo/16.png
|
Before Width: | Height: | Size: 566 B After Width: | Height: | Size: 475 B |
BIN
Logo/256.png
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 11 KiB |
BIN
Logo/32.png
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
BIN
Logo/400.png
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 19 KiB |
BIN
Logo/48.png
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.8 KiB |
BIN
Logo/512.png
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 23 KiB |
BIN
Logo/64.png
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.0 KiB |
BIN
Logo/800.png
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 3.2 KiB |
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
### Readarr is in early stages of development, alpha/beta binary builds are not yet available. Use of any test builds isn't recommend, and may have detrimental effects on your library.
|
### Readarr is in early stages of development, alpha/beta binary builds are not yet available. Use of any test builds isn't recommend, and may have detrimental effects on your library.
|
||||||
|
|
||||||
Readarr is a ebook/magazine/audiobook collection manager for Usenet and BitTorrent users. It can monitor multiple RSS feeds for new books from your favorite authors and will grab, sort and rename them.
|
Readarr is a ebook (and maybe eventually magazine/audiobook) collection manager for Usenet and BitTorrent users. It can monitor multiple RSS feeds for new books from your favorite authors and will grab, sort and rename them.
|
||||||
|
|
||||||
## Major Features Include:
|
## Major Features Include:
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ Readarr is a ebook/magazine/audiobook collection manager for Usenet and BitTorre
|
|||||||
* Manual search so you can pick any release or to see why a release was not downloaded automatically
|
* Manual search so you can pick any release or to see why a release was not downloaded automatically
|
||||||
* Fully configurable book renaming
|
* Fully configurable book renaming
|
||||||
* Full integration with SABnzbd and NZBGet
|
* Full integration with SABnzbd and NZBGet
|
||||||
* Full integration with Kodi, Plex (notification, library update, metadata)
|
* Full integration with Calibre (add to library, conversion)
|
||||||
* And a beautiful UI
|
* And a beautiful UI
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ variables:
|
|||||||
minorVersion: $[counter('minorVersion', 1)]
|
minorVersion: $[counter('minorVersion', 1)]
|
||||||
readarrVersion: '$(majorVersion).$(minorVersion)'
|
readarrVersion: '$(majorVersion).$(minorVersion)'
|
||||||
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
|
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
|
||||||
sentryOrg: 'radarr'
|
sentryOrg: 'servarr'
|
||||||
|
sentryUrl: 'https://sentry.servarr.com'
|
||||||
dotnetVersion: '3.1.102'
|
dotnetVersion: '3.1.102'
|
||||||
|
|
||||||
trigger:
|
trigger:
|
||||||
@@ -255,7 +256,6 @@ stages:
|
|||||||
sentry-cli releases deploys "${RELEASENAME}" new -e production
|
sentry-cli releases deploys "${RELEASENAME}" new -e production
|
||||||
fi
|
fi
|
||||||
displayName: Publish Sentry Source Maps
|
displayName: Publish Sentry Source Maps
|
||||||
enabled: false
|
|
||||||
condition: |
|
condition: |
|
||||||
or
|
or
|
||||||
(
|
(
|
||||||
@@ -263,9 +263,9 @@ stages:
|
|||||||
and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
|
and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
|
||||||
)
|
)
|
||||||
env:
|
env:
|
||||||
SENTRY_AUTH_TOKEN: $(sentryAuthTokenSelfHosted)
|
SENTRY_AUTH_TOKEN: $(sentryAuthTokenServarr)
|
||||||
SENTRY_ORG: $(sentryOrg)
|
SENTRY_ORG: $(sentryOrg)
|
||||||
SENTRY_URL: https://sentry.radarr.video
|
SENTRY_URL: $(sentryUrl)
|
||||||
|
|
||||||
- stage: Unit_Test
|
- stage: Unit_Test
|
||||||
displayName: Unit Tests
|
displayName: Unit Tests
|
||||||
@@ -338,15 +338,12 @@ stages:
|
|||||||
testResultsFormat: 'NUnit'
|
testResultsFormat: 'NUnit'
|
||||||
testResultsFiles: '**/TestResult.xml'
|
testResultsFiles: '**/TestResult.xml'
|
||||||
testRunTitle: '$(testName) Unit Tests'
|
testRunTitle: '$(testName) Unit Tests'
|
||||||
failTaskOnFailedTests: false
|
failTaskOnFailedTests: true
|
||||||
|
|
||||||
- job: Unit_Docker
|
- job: Unit_Docker
|
||||||
displayName: Unit Docker
|
displayName: Unit Docker
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
mono508:
|
|
||||||
testName: 'Mono 5.8'
|
|
||||||
containerImage: lidarr/testimages:mono-5.8
|
|
||||||
mono520:
|
mono520:
|
||||||
testName: 'Mono 5.20'
|
testName: 'Mono 5.20'
|
||||||
containerImage: lidarr/testimages:mono-5.20
|
containerImage: lidarr/testimages:mono-5.20
|
||||||
@@ -463,16 +460,13 @@ stages:
|
|||||||
testResultsFormat: 'NUnit'
|
testResultsFormat: 'NUnit'
|
||||||
testResultsFiles: '**/TestResult.xml'
|
testResultsFiles: '**/TestResult.xml'
|
||||||
testRunTitle: '$(testName) Integration Tests'
|
testRunTitle: '$(testName) Integration Tests'
|
||||||
failTaskOnFailedTests: false
|
failTaskOnFailedTests: true
|
||||||
displayName: Publish Test Results
|
displayName: Publish Test Results
|
||||||
|
|
||||||
- job: Integration_Docker
|
- job: Integration_Docker
|
||||||
displayName: Integration Docker
|
displayName: Integration Docker
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
mono508:
|
|
||||||
testName: 'Mono 5.8'
|
|
||||||
containerImage: lidarr/testimages:mono-5.8
|
|
||||||
mono520:
|
mono520:
|
||||||
testName: 'Mono 5.20'
|
testName: 'Mono 5.20'
|
||||||
containerImage: lidarr/testimages:mono-5.20
|
containerImage: lidarr/testimages:mono-5.20
|
||||||
@@ -557,7 +551,7 @@ stages:
|
|||||||
osName: 'Windows'
|
osName: 'Windows'
|
||||||
imageName: 'windows-2019'
|
imageName: 'windows-2019'
|
||||||
pattern: 'Readarr.**.windows-core-x64.zip'
|
pattern: 'Readarr.**.windows-core-x64.zip'
|
||||||
failBuild: true
|
failBuild: $(failOnAutomationFailure)
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: $(imageName)
|
vmImage: $(imageName)
|
||||||
@@ -713,3 +707,25 @@ stages:
|
|||||||
codeCoverageTool: 'cobertura'
|
codeCoverageTool: 'cobertura'
|
||||||
summaryFileLocation: './CoverageResults/combined/Cobertura.xml'
|
summaryFileLocation: './CoverageResults/combined/Cobertura.xml'
|
||||||
reportDirectory: './CoverageResults/combined/'
|
reportDirectory: './CoverageResults/combined/'
|
||||||
|
|
||||||
|
- stage: Report_Out
|
||||||
|
dependsOn:
|
||||||
|
- Analyze
|
||||||
|
- Unit_Test
|
||||||
|
- Integration
|
||||||
|
- Automation
|
||||||
|
condition: always()
|
||||||
|
displayName: Build Status Report
|
||||||
|
jobs:
|
||||||
|
- job:
|
||||||
|
displayName: Discord Notification
|
||||||
|
pool:
|
||||||
|
vmImage: 'ubuntu-18.04'
|
||||||
|
steps:
|
||||||
|
- checkout: none
|
||||||
|
- powershell: |
|
||||||
|
iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/Servarr/AzureDiscordNotify/master/DiscordNotify.ps1'))
|
||||||
|
env:
|
||||||
|
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||||
|
DISCORDCHANNELID: $(discordChannelId)
|
||||||
|
DISCORDWEBHOOKKEY: $(discordWebhookKey)
|
||||||
|
|||||||
2
debian/control
vendored
@@ -2,7 +2,7 @@ Section: web
|
|||||||
Priority: optional
|
Priority: optional
|
||||||
Maintainer: Sonarr <contact@nzbdrone.com>
|
Maintainer: Sonarr <contact@nzbdrone.com>
|
||||||
Source: nzbdrone
|
Source: nzbdrone
|
||||||
Homepage: https://readarr.audio
|
Homepage: https://readarr.com
|
||||||
Vcs-Git: git@github.com:readarr/Readarr.git
|
Vcs-Git: git@github.com:readarr/Readarr.git
|
||||||
Vcs-Browser: https://github.com/readarr/Readarr
|
Vcs-Browser: https://github.com/readarr/Readarr
|
||||||
|
|
||||||
|
|||||||
2
debian/copyright
vendored
@@ -3,7 +3,7 @@ Upstream-Name: nzbdrone
|
|||||||
Source: https://github.com/readarr/Readarr
|
Source: https://github.com/readarr/Readarr
|
||||||
|
|
||||||
Files: *
|
Files: *
|
||||||
Copyright: 2010-2016 Readarr <hello@readarr.audio>
|
Copyright: 2010-2016 Readarr <hello@readarr.com>
|
||||||
|
|
||||||
License: GPL-3.0+
|
License: GPL-3.0+
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { icons, kinds } from 'Helpers/Props';
|
import { icons, kinds } from 'Helpers/Props';
|
||||||
import IconButton from 'Components/Link/IconButton';
|
import IconButton from 'Components/Link/IconButton';
|
||||||
@@ -67,11 +68,11 @@ class BlacklistRow extends Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name === 'artist.sortName') {
|
if (name === 'authors.sortName') {
|
||||||
return (
|
return (
|
||||||
<TableRowCell key={name}>
|
<TableRowCell key={name}>
|
||||||
<ArtistNameLink
|
<ArtistNameLink
|
||||||
foreignArtistId={artist.foreignArtistId}
|
titleSlug={artist.titleSlug}
|
||||||
artistName={artist.artistName}
|
artistName={artist.artistName}
|
||||||
/>
|
/>
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|||||||
@@ -61,10 +61,10 @@ class HistoryConnector extends Component {
|
|||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (hasDifferentItems(prevProps.items, this.props.items)) {
|
if (hasDifferentItems(prevProps.items, this.props.items)) {
|
||||||
const albumIds = selectUniqueIds(this.props.items, 'albumId');
|
const bookIds = selectUniqueIds(this.props.items, 'bookId');
|
||||||
const trackIds = selectUniqueIds(this.props.items, 'trackId');
|
const trackIds = selectUniqueIds(this.props.items, 'trackId');
|
||||||
if (albumIds.length) {
|
if (bookIds.length) {
|
||||||
this.props.fetchAlbums({ albumIds });
|
this.props.fetchAlbums({ bookIds });
|
||||||
} else {
|
} else {
|
||||||
this.props.clearAlbums();
|
this.props.clearAlbums();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ class HistoryRow extends Component {
|
|||||||
const {
|
const {
|
||||||
artist,
|
artist,
|
||||||
album,
|
album,
|
||||||
track,
|
|
||||||
quality,
|
quality,
|
||||||
qualityCutoffNotMet,
|
qualityCutoffNotMet,
|
||||||
eventType,
|
eventType,
|
||||||
@@ -94,22 +93,22 @@ class HistoryRow extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name === 'artist.sortName') {
|
if (name === 'authors.sortName') {
|
||||||
return (
|
return (
|
||||||
<TableRowCell key={name}>
|
<TableRowCell key={name}>
|
||||||
<ArtistNameLink
|
<ArtistNameLink
|
||||||
foreignArtistId={artist.foreignArtistId}
|
titleSlug={artist.titleSlug}
|
||||||
artistName={artist.artistName}
|
artistName={artist.artistName}
|
||||||
/>
|
/>
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name === 'album.title') {
|
if (name === 'books.title') {
|
||||||
return (
|
return (
|
||||||
<TableRowCell key={name}>
|
<TableRowCell key={name}>
|
||||||
<AlbumTitleLink
|
<AlbumTitleLink
|
||||||
foreignAlbumId={album.foreignAlbumId}
|
titleSlug={album.titleSlug}
|
||||||
title={album.title}
|
title={album.title}
|
||||||
disambiguation={album.disambiguation}
|
disambiguation={album.disambiguation}
|
||||||
/>
|
/>
|
||||||
@@ -117,14 +116,6 @@ class HistoryRow extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name === 'trackTitle') {
|
|
||||||
return (
|
|
||||||
<TableRowCell key={name}>
|
|
||||||
{track.title}
|
|
||||||
</TableRowCell>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'quality') {
|
if (name === 'quality') {
|
||||||
return (
|
return (
|
||||||
<TableRowCell key={name}>
|
<TableRowCell key={name}>
|
||||||
@@ -214,10 +205,9 @@ class HistoryRow extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HistoryRow.propTypes = {
|
HistoryRow.propTypes = {
|
||||||
albumId: PropTypes.number,
|
bookId: PropTypes.number,
|
||||||
artist: PropTypes.object.isRequired,
|
artist: PropTypes.object.isRequired,
|
||||||
album: PropTypes.object,
|
album: PropTypes.object,
|
||||||
track: PropTypes.object,
|
|
||||||
quality: PropTypes.object.isRequired,
|
quality: PropTypes.object.isRequired,
|
||||||
qualityCutoffNotMet: PropTypes.bool.isRequired,
|
qualityCutoffNotMet: PropTypes.bool.isRequired,
|
||||||
eventType: PropTypes.string.isRequired,
|
eventType: PropTypes.string.isRequired,
|
||||||
@@ -232,10 +222,4 @@ HistoryRow.propTypes = {
|
|||||||
onMarkAsFailedPress: PropTypes.func.isRequired
|
onMarkAsFailedPress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
HistoryRow.defaultProps = {
|
|
||||||
track: {
|
|
||||||
title: ''
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default HistoryRow;
|
export default HistoryRow;
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class Queue extends Component {
|
|||||||
this.props.isFetching &&
|
this.props.isFetching &&
|
||||||
nextProps.isPopulated &&
|
nextProps.isPopulated &&
|
||||||
hasDifferentItems(this.props.items, nextProps.items) &&
|
hasDifferentItems(this.props.items, nextProps.items) &&
|
||||||
nextProps.items.some((e) => e.albumId)
|
nextProps.items.some((e) => e.bookId)
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -146,7 +146,7 @@ class Queue extends Component {
|
|||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const isRefreshing = isFetching || isAlbumsFetching || isRefreshMonitoredDownloadsExecuting;
|
const isRefreshing = isFetching || isAlbumsFetching || isRefreshMonitoredDownloadsExecuting;
|
||||||
const isAllPopulated = isPopulated && (isAlbumsPopulated || !items.length || items.every((e) => !e.albumId));
|
const isAllPopulated = isPopulated && (isAlbumsPopulated || !items.length || items.every((e) => !e.bookId));
|
||||||
const hasError = error || albumsError;
|
const hasError = error || albumsError;
|
||||||
const selectedIds = this.getSelectedIds();
|
const selectedIds = this.getSelectedIds();
|
||||||
const selectedCount = selectedIds.length;
|
const selectedCount = selectedIds.length;
|
||||||
@@ -236,7 +236,7 @@ class Queue extends Component {
|
|||||||
return (
|
return (
|
||||||
<QueueRowConnector
|
<QueueRowConnector
|
||||||
key={item.id}
|
key={item.id}
|
||||||
albumId={item.albumId}
|
bookId={item.bookId}
|
||||||
isSelected={selectedState[item.id]}
|
isSelected={selectedState[item.id]}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
{...item}
|
{...item}
|
||||||
@@ -264,7 +264,7 @@ class Queue extends Component {
|
|||||||
selectedIds.every((id) => {
|
selectedIds.every((id) => {
|
||||||
const item = items.find((i) => i.id === id);
|
const item = items.find((i) => i.id === id);
|
||||||
|
|
||||||
return !!(item && item.artistId && item.albumId);
|
return !!(item && item.authorId && item.bookId);
|
||||||
})
|
})
|
||||||
)}
|
)}
|
||||||
onRemovePress={this.onRemoveSelectedConfirmed}
|
onRemovePress={this.onRemoveSelectedConfirmed}
|
||||||
|
|||||||
@@ -62,10 +62,10 @@ class QueueConnector extends Component {
|
|||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (hasDifferentItems(prevProps.items, this.props.items)) {
|
if (hasDifferentItems(prevProps.items, this.props.items)) {
|
||||||
const albumIds = selectUniqueIds(this.props.items, 'albumId');
|
const bookIds = selectUniqueIds(this.props.items, 'bookId');
|
||||||
|
|
||||||
if (albumIds.length) {
|
if (bookIds.length) {
|
||||||
this.props.fetchAlbums({ albumIds });
|
this.props.fetchAlbums({ bookIds });
|
||||||
} else {
|
} else {
|
||||||
this.props.clearAlbums();
|
this.props.clearAlbums();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,13 +137,13 @@ class QueueRow extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name === 'artist.sortName') {
|
if (name === 'authors.sortName') {
|
||||||
return (
|
return (
|
||||||
<TableRowCell key={name}>
|
<TableRowCell key={name}>
|
||||||
{
|
{
|
||||||
artist ?
|
artist ?
|
||||||
<ArtistNameLink
|
<ArtistNameLink
|
||||||
foreignArtistId={artist.foreignArtistId}
|
titleSlug={artist.titleSlug}
|
||||||
artistName={artist.artistName}
|
artistName={artist.artistName}
|
||||||
/> :
|
/> :
|
||||||
title
|
title
|
||||||
@@ -152,13 +152,13 @@ class QueueRow extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name === 'album.title') {
|
if (name === 'books.title') {
|
||||||
return (
|
return (
|
||||||
<TableRowCell key={name}>
|
<TableRowCell key={name}>
|
||||||
{
|
{
|
||||||
album ?
|
album ?
|
||||||
<AlbumTitleLink
|
<AlbumTitleLink
|
||||||
foreignAlbumId={album.foreignAlbumId}
|
titleSlug={album.titleSlug}
|
||||||
title={album.title}
|
title={album.title}
|
||||||
disambiguation={album.disambiguation}
|
disambiguation={album.disambiguation}
|
||||||
/> :
|
/> :
|
||||||
@@ -168,7 +168,7 @@ class QueueRow extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name === 'album.releaseDate') {
|
if (name === 'books.releaseDate') {
|
||||||
if (album) {
|
if (album) {
|
||||||
return (
|
return (
|
||||||
<RelativeDateCellConnector
|
<RelativeDateCellConnector
|
||||||
|
|||||||
@@ -6,38 +6,38 @@ function ArtistMonitoringOptionsPopoverContent() {
|
|||||||
return (
|
return (
|
||||||
<DescriptionList>
|
<DescriptionList>
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title="All Albums"
|
title="All Books"
|
||||||
data="Monitor all albums except specials"
|
data="Monitor all books"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title="Future Albums"
|
title="Future Books"
|
||||||
data="Monitor albums that have not released yet"
|
data="Monitor books that have not released yet"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title="Missing Albums"
|
title="Missing Books"
|
||||||
data="Monitor albums that do not have files or have not released yet"
|
data="Monitor books that do not have files or have not released yet"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title="Existing Albums"
|
title="Existing Books"
|
||||||
data="Monitor albums that have files or have not released yet"
|
data="Monitor books that have files or have not released yet"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title="First Album"
|
title="First Book"
|
||||||
data="Monitor the first albums. All other albums will be ignored"
|
data="Monitor the first book. All other books will be ignored"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title="Latest Album"
|
title="Latest Book"
|
||||||
data="Monitor the latest albums and future albums"
|
data="Monitor the latest book and future books"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title="None"
|
title="None"
|
||||||
data="No albums will be monitored"
|
data="No books will be monitored"
|
||||||
/>
|
/>
|
||||||
</DescriptionList>
|
</DescriptionList>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class AlbumSearchCell extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
albumId,
|
bookId,
|
||||||
albumTitle,
|
albumTitle,
|
||||||
isSearching,
|
isSearching,
|
||||||
onSearchPress,
|
onSearchPress,
|
||||||
@@ -58,7 +58,7 @@ class AlbumSearchCell extends Component {
|
|||||||
|
|
||||||
<AlbumInteractiveSearchModalConnector
|
<AlbumInteractiveSearchModalConnector
|
||||||
isOpen={this.state.isDetailsModalOpen}
|
isOpen={this.state.isDetailsModalOpen}
|
||||||
albumId={albumId}
|
bookId={bookId}
|
||||||
albumTitle={albumTitle}
|
albumTitle={albumTitle}
|
||||||
onModalClose={this.onDetailsModalClose}
|
onModalClose={this.onDetailsModalClose}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
@@ -70,8 +70,8 @@ class AlbumSearchCell extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AlbumSearchCell.propTypes = {
|
AlbumSearchCell.propTypes = {
|
||||||
albumId: PropTypes.number.isRequired,
|
bookId: PropTypes.number.isRequired,
|
||||||
artistId: PropTypes.number.isRequired,
|
authorId: PropTypes.number.isRequired,
|
||||||
albumTitle: PropTypes.string.isRequired,
|
albumTitle: PropTypes.string.isRequired,
|
||||||
isSearching: PropTypes.bool.isRequired,
|
isSearching: PropTypes.bool.isRequired,
|
||||||
onSearchPress: PropTypes.func.isRequired
|
onSearchPress: PropTypes.func.isRequired
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import AlbumSearchCell from './AlbumSearchCell';
|
|||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state, { albumId }) => albumId,
|
(state, { bookId }) => bookId,
|
||||||
createArtistSelector(),
|
createArtistSelector(),
|
||||||
createCommandsSelector(),
|
createCommandsSelector(),
|
||||||
(albumId, artist, commands) => {
|
(bookId, artist, commands) => {
|
||||||
const isSearching = commands.some((command) => {
|
const isSearching = commands.some((command) => {
|
||||||
const albumSearch = command.name === commandNames.ALBUM_SEARCH;
|
const albumSearch = command.name === commandNames.ALBUM_SEARCH;
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ function createMapStateToProps() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
isCommandExecuting(command) &&
|
isCommandExecuting(command) &&
|
||||||
command.body.albumIds.indexOf(albumId) > -1
|
command.body.bookIds.indexOf(bookId) > -1
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
onSearchPress(name, path) {
|
onSearchPress(name, path) {
|
||||||
dispatch(executeCommand({
|
dispatch(executeCommand({
|
||||||
name: commandNames.ALBUM_SEARCH,
|
name: commandNames.ALBUM_SEARCH,
|
||||||
albumIds: [props.albumId]
|
bookIds: [props.bookId]
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import PropTypes from 'prop-types';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
|
|
||||||
function AlbumTitleLink({ foreignAlbumId, title, disambiguation }) {
|
function AlbumTitleLink({ titleSlug, title, disambiguation }) {
|
||||||
const link = `/album/${foreignAlbumId}`;
|
const link = `/book/${titleSlug}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to={link}>
|
<Link to={link}>
|
||||||
@@ -13,7 +13,7 @@ function AlbumTitleLink({ foreignAlbumId, title, disambiguation }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AlbumTitleLink.propTypes = {
|
AlbumTitleLink.propTypes = {
|
||||||
foreignAlbumId: PropTypes.string.isRequired,
|
titleSlug: PropTypes.string.isRequired,
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
disambiguation: PropTypes.string
|
disambiguation: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,14 +28,14 @@ class DeleteAlbumModalContentConnector extends Component {
|
|||||||
|
|
||||||
onDeletePress = (deleteFiles, addImportListExclusion) => {
|
onDeletePress = (deleteFiles, addImportListExclusion) => {
|
||||||
this.props.deleteAlbum({
|
this.props.deleteAlbum({
|
||||||
id: this.props.albumId,
|
id: this.props.bookId,
|
||||||
deleteFiles,
|
deleteFiles,
|
||||||
addImportListExclusion
|
addImportListExclusion
|
||||||
});
|
});
|
||||||
|
|
||||||
this.props.onModalClose(true);
|
this.props.onModalClose(true);
|
||||||
|
|
||||||
this.props.push(`${window.Readarr.urlBase}/artist/${this.props.foreignArtistId}`);
|
this.props.push(`${window.Readarr.urlBase}/author/${this.props.titleSlug}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -52,8 +52,8 @@ class DeleteAlbumModalContentConnector extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DeleteAlbumModalContentConnector.propTypes = {
|
DeleteAlbumModalContentConnector.propTypes = {
|
||||||
albumId: PropTypes.number.isRequired,
|
bookId: PropTypes.number.isRequired,
|
||||||
foreignArtistId: PropTypes.string.isRequired,
|
titleSlug: PropTypes.string.isRequired,
|
||||||
push: PropTypes.func.isRequired,
|
push: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired,
|
onModalClose: PropTypes.func.isRequired,
|
||||||
deleteAlbum: PropTypes.func.isRequired
|
deleteAlbum: PropTypes.func.isRequired
|
||||||
|
|||||||
@@ -35,7 +35,6 @@
|
|||||||
.cover {
|
.cover {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-right: 35px;
|
margin-right: 35px;
|
||||||
width: 250px;
|
|
||||||
height: 250px;
|
height: 250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,6 +135,31 @@
|
|||||||
padding: 20px;
|
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) {
|
@media only screen and (max-width: $breakpointSmall) {
|
||||||
.contentContainer {
|
.contentContainer {
|
||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import _ from 'lodash';
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
|
||||||
import TextTruncate from 'react-text-truncate';
|
import TextTruncate from 'react-text-truncate';
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
import selectAll from 'Utilities/Table/selectAll';
|
import selectAll from 'Utilities/Table/selectAll';
|
||||||
@@ -16,8 +17,7 @@ import MonitorToggleButton from 'Components/MonitorToggleButton';
|
|||||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||||
import AlbumCover from 'Album/AlbumCover';
|
import AlbumCover from 'Album/AlbumCover';
|
||||||
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
|
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
|
||||||
import RetagPreviewModalConnector from 'Retag/RetagPreviewModalConnector';
|
// import RetagPreviewModalConnector from 'Retag/RetagPreviewModalConnector';
|
||||||
import EditAlbumModalConnector from 'Album/Edit/EditAlbumModalConnector';
|
|
||||||
import DeleteAlbumModal from 'Album/Delete/DeleteAlbumModal';
|
import DeleteAlbumModal from 'Album/Delete/DeleteAlbumModal';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
import PageContent from 'Components/Page/PageContent';
|
import PageContent from 'Components/Page/PageContent';
|
||||||
@@ -26,10 +26,10 @@ import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
|||||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||||
import AlbumDetailsMediumConnector from './AlbumDetailsMediumConnector';
|
import ArtistHistoryTable from 'Artist/History/ArtistHistoryTable';
|
||||||
import ArtistHistoryModal from 'Artist/History/ArtistHistoryModal';
|
import InteractiveSearchTable from 'InteractiveSearch/InteractiveSearchTable';
|
||||||
import AlbumInteractiveSearchModalConnector from 'Album/Search/AlbumInteractiveSearchModalConnector';
|
import InteractiveSearchFilterMenuConnector from 'InteractiveSearch/InteractiveSearchFilterMenuConnector';
|
||||||
import TrackFileEditorModal from 'TrackFile/Editor/TrackFileEditorModal';
|
import TrackFileEditorTable from 'TrackFile/Editor/TrackFileEditorTable';
|
||||||
import AlbumDetailsLinks from './AlbumDetailsLinks';
|
import AlbumDetailsLinks from './AlbumDetailsLinks';
|
||||||
import styles from './AlbumDetails.css';
|
import styles from './AlbumDetails.css';
|
||||||
|
|
||||||
@@ -85,14 +85,11 @@ class AlbumDetails extends Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
isOrganizeModalOpen: false,
|
isOrganizeModalOpen: false,
|
||||||
isRetagModalOpen: false,
|
isRetagModalOpen: false,
|
||||||
isArtistHistoryModalOpen: false,
|
|
||||||
isInteractiveSearchModalOpen: false,
|
|
||||||
isManageTracksOpen: false,
|
|
||||||
isEditAlbumModalOpen: false,
|
|
||||||
isDeleteAlbumModalOpen: false,
|
isDeleteAlbumModalOpen: false,
|
||||||
allExpanded: false,
|
allExpanded: false,
|
||||||
allCollapsed: false,
|
allCollapsed: false,
|
||||||
expandedState: {}
|
expandedState: {},
|
||||||
|
selectedTabIndex: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,17 +112,8 @@ class AlbumDetails extends Component {
|
|||||||
this.setState({ isRetagModalOpen: false });
|
this.setState({ isRetagModalOpen: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
onEditAlbumPress = () => {
|
|
||||||
this.setState({ isEditAlbumModalOpen: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
onEditAlbumModalClose = () => {
|
|
||||||
this.setState({ isEditAlbumModalOpen: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
onDeleteAlbumPress = () => {
|
onDeleteAlbumPress = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
isEditAlbumModalOpen: false,
|
|
||||||
isDeleteAlbumModalOpen: true
|
isDeleteAlbumModalOpen: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -134,30 +122,6 @@ class AlbumDetails extends Component {
|
|||||||
this.setState({ isDeleteAlbumModalOpen: false });
|
this.setState({ isDeleteAlbumModalOpen: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
onManageTracksPress = () => {
|
|
||||||
this.setState({ isManageTracksOpen: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
onManageTracksModalClose = () => {
|
|
||||||
this.setState({ isManageTracksOpen: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
onInteractiveSearchPress = () => {
|
|
||||||
this.setState({ isInteractiveSearchModalOpen: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
onInteractiveSearchModalClose = () => {
|
|
||||||
this.setState({ isInteractiveSearchModalOpen: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
onArtistHistoryPress = () => {
|
|
||||||
this.setState({ isArtistHistoryModalOpen: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
onArtistHistoryModalClose = () => {
|
|
||||||
this.setState({ isArtistHistoryModalOpen: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
onExpandAllPress = () => {
|
onExpandAllPress = () => {
|
||||||
const {
|
const {
|
||||||
allExpanded,
|
allExpanded,
|
||||||
@@ -167,7 +131,7 @@ class AlbumDetails extends Component {
|
|||||||
this.setState(getExpandedState(selectAll(expandedState, !allExpanded)));
|
this.setState(getExpandedState(selectAll(expandedState, !allExpanded)));
|
||||||
}
|
}
|
||||||
|
|
||||||
onExpandPress = (albumId, isExpanded) => {
|
onExpandPress = (bookId, isExpanded) => {
|
||||||
this.setState((state) => {
|
this.setState((state) => {
|
||||||
const convertedState = {
|
const convertedState = {
|
||||||
allSelected: state.allExpanded,
|
allSelected: state.allExpanded,
|
||||||
@@ -175,7 +139,7 @@ class AlbumDetails extends Component {
|
|||||||
selectedState: state.expandedState
|
selectedState: state.expandedState
|
||||||
};
|
};
|
||||||
|
|
||||||
const newState = toggleSelected(convertedState, [], albumId, isExpanded, false);
|
const newState = toggleSelected(convertedState, [], bookId, isExpanded, false);
|
||||||
|
|
||||||
return getExpandedState(newState);
|
return getExpandedState(newState);
|
||||||
});
|
});
|
||||||
@@ -187,23 +151,20 @@ class AlbumDetails extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
foreignAlbumId,
|
titleSlug,
|
||||||
title,
|
title,
|
||||||
disambiguation,
|
disambiguation,
|
||||||
duration,
|
duration,
|
||||||
overview,
|
overview,
|
||||||
albumType,
|
|
||||||
statistics = {},
|
statistics = {},
|
||||||
monitored,
|
monitored,
|
||||||
releaseDate,
|
releaseDate,
|
||||||
ratings,
|
ratings,
|
||||||
images,
|
images,
|
||||||
links,
|
links,
|
||||||
media,
|
|
||||||
isSaving,
|
isSaving,
|
||||||
isFetching,
|
isFetching,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
albumsError,
|
|
||||||
trackFilesError,
|
trackFilesError,
|
||||||
hasTrackFiles,
|
hasTrackFiles,
|
||||||
shortDateFormat,
|
shortDateFormat,
|
||||||
@@ -217,15 +178,11 @@ class AlbumDetails extends Component {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
isOrganizeModalOpen,
|
isOrganizeModalOpen,
|
||||||
isRetagModalOpen,
|
// isRetagModalOpen,
|
||||||
isArtistHistoryModalOpen,
|
|
||||||
isInteractiveSearchModalOpen,
|
|
||||||
isEditAlbumModalOpen,
|
|
||||||
isDeleteAlbumModalOpen,
|
isDeleteAlbumModalOpen,
|
||||||
isManageTracksOpen,
|
|
||||||
allExpanded,
|
allExpanded,
|
||||||
allCollapsed,
|
allCollapsed,
|
||||||
expandedState
|
selectedTabIndex
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
let expandIcon = icons.EXPAND_INDETERMINATE;
|
let expandIcon = icons.EXPAND_INDETERMINATE;
|
||||||
@@ -247,12 +204,6 @@ class AlbumDetails extends Component {
|
|||||||
onPress={onSearchPress}
|
onPress={onSearchPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PageToolbarButton
|
|
||||||
label="Interactive Search"
|
|
||||||
iconName={icons.INTERACTIVE}
|
|
||||||
onPress={this.onInteractiveSearchPress}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PageToolbarSeparator />
|
<PageToolbarSeparator />
|
||||||
|
|
||||||
<PageToolbarButton
|
<PageToolbarButton
|
||||||
@@ -269,27 +220,8 @@ class AlbumDetails extends Component {
|
|||||||
onPress={this.onRetagPress}
|
onPress={this.onRetagPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PageToolbarButton
|
|
||||||
label="Manage Tracks"
|
|
||||||
iconName={icons.TRACK_FILE}
|
|
||||||
isDisabled={!hasTrackFiles}
|
|
||||||
onPress={this.onManageTracksPress}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PageToolbarButton
|
|
||||||
label="History"
|
|
||||||
iconName={icons.HISTORY}
|
|
||||||
onPress={this.onArtistHistoryPress}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PageToolbarSeparator />
|
<PageToolbarSeparator />
|
||||||
|
|
||||||
<PageToolbarButton
|
|
||||||
label="Edit"
|
|
||||||
iconName={icons.EDIT}
|
|
||||||
onPress={this.onEditAlbumPress}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PageToolbarButton
|
<PageToolbarButton
|
||||||
label="Delete"
|
label="Delete"
|
||||||
iconName={icons.DELETE}
|
iconName={icons.DELETE}
|
||||||
@@ -350,7 +282,7 @@ class AlbumDetails extends Component {
|
|||||||
name={icons.ARROW_LEFT}
|
name={icons.ARROW_LEFT}
|
||||||
size={30}
|
size={30}
|
||||||
title={`Go to ${previousAlbum.title}`}
|
title={`Go to ${previousAlbum.title}`}
|
||||||
to={`/album/${previousAlbum.foreignAlbumId}`}
|
to={`/book/${previousAlbum.titleSlug}`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -358,7 +290,7 @@ class AlbumDetails extends Component {
|
|||||||
name={icons.ARROW_UP}
|
name={icons.ARROW_UP}
|
||||||
size={30}
|
size={30}
|
||||||
title={`Go to ${artist.artistName}`}
|
title={`Go to ${artist.artistName}`}
|
||||||
to={`/artist/${artist.foreignArtistId}`}
|
to={`/author/${artist.titleSlug}`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -366,7 +298,7 @@ class AlbumDetails extends Component {
|
|||||||
name={icons.ARROW_RIGHT}
|
name={icons.ARROW_RIGHT}
|
||||||
size={30}
|
size={30}
|
||||||
title={`Go to ${nextAlbum.title}`}
|
title={`Go to ${nextAlbum.title}`}
|
||||||
to={`/album/${nextAlbum.foreignAlbumId}`}
|
to={`/book/${nextAlbum.titleSlug}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -435,24 +367,6 @@ class AlbumDetails extends Component {
|
|||||||
</span>
|
</span>
|
||||||
</Label>
|
</Label>
|
||||||
|
|
||||||
{
|
|
||||||
!!albumType &&
|
|
||||||
<Label
|
|
||||||
className={styles.detailsLabel}
|
|
||||||
title="Type"
|
|
||||||
size={sizes.LARGE}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
name={icons.INFO}
|
|
||||||
size={17}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<span className={styles.qualityProfileName}>
|
|
||||||
{albumType}
|
|
||||||
</span>
|
|
||||||
</Label>
|
|
||||||
}
|
|
||||||
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
anchor={
|
anchor={
|
||||||
<Label
|
<Label
|
||||||
@@ -471,7 +385,7 @@ class AlbumDetails extends Component {
|
|||||||
}
|
}
|
||||||
tooltip={
|
tooltip={
|
||||||
<AlbumDetailsLinks
|
<AlbumDetailsLinks
|
||||||
foreignAlbumId={foreignAlbumId}
|
titleSlug={titleSlug}
|
||||||
links={links}
|
links={links}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@@ -483,7 +397,7 @@ class AlbumDetails extends Component {
|
|||||||
<div className={styles.overview}>
|
<div className={styles.overview}>
|
||||||
<TextTruncate
|
<TextTruncate
|
||||||
line={Math.floor(125 / (defaultFontSize * lineHeight))}
|
line={Math.floor(125 / (defaultFontSize * lineHeight))}
|
||||||
text={overview}
|
text={overview.replace(/<[^>]*>?/gm, '')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -492,90 +406,92 @@ class AlbumDetails extends Component {
|
|||||||
|
|
||||||
<div className={styles.contentContainer}>
|
<div className={styles.contentContainer}>
|
||||||
{
|
{
|
||||||
!isPopulated && !albumsError && !trackFilesError &&
|
!isPopulated && !trackFilesError &&
|
||||||
<LoadingIndicator />
|
<LoadingIndicator />
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
!isFetching && albumsError &&
|
|
||||||
<div>Loading albums failed</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
!isFetching && trackFilesError &&
|
!isFetching && trackFilesError &&
|
||||||
<div>Loading track files failed</div>
|
<div>Loading book files failed</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
<Tabs selectedIndex={this.state.tabIndex} onSelect={(tabIndex) => this.setState({ selectedTabIndex: tabIndex })}>
|
||||||
isPopulated && !!media.length &&
|
<TabList
|
||||||
<div>
|
className={styles.tabList}
|
||||||
|
>
|
||||||
|
<Tab
|
||||||
|
className={styles.tab}
|
||||||
|
selectedClassName={styles.selectedTab}
|
||||||
|
>
|
||||||
|
History
|
||||||
|
</Tab>
|
||||||
|
|
||||||
{
|
<Tab
|
||||||
media.slice(0).map((medium) => {
|
className={styles.tab}
|
||||||
return (
|
selectedClassName={styles.selectedTab}
|
||||||
<AlbumDetailsMediumConnector
|
>
|
||||||
key={medium.mediumNumber}
|
Search
|
||||||
albumId={id}
|
</Tab>
|
||||||
albumMonitored={monitored}
|
|
||||||
{...medium}
|
|
||||||
isExpanded={expandedState[medium.mediumNumber]}
|
|
||||||
onExpandPress={this.onExpandPress}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
|
<Tab
|
||||||
|
className={styles.tab}
|
||||||
|
selectedClassName={styles.selectedTab}
|
||||||
|
>
|
||||||
|
Files
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
{
|
||||||
|
selectedTabIndex === 1 &&
|
||||||
|
<div className={styles.filterIcon}>
|
||||||
|
<InteractiveSearchFilterMenuConnector
|
||||||
|
type="album"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
</TabList>
|
||||||
|
|
||||||
|
<TabPanel>
|
||||||
|
<ArtistHistoryTable
|
||||||
|
authorId={artist.id}
|
||||||
|
bookId={id}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
|
||||||
|
<TabPanel>
|
||||||
|
<InteractiveSearchTable
|
||||||
|
bookId={id}
|
||||||
|
type="album"
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
|
||||||
|
<TabPanel>
|
||||||
|
<TrackFileEditorTable
|
||||||
|
authorId={artist.id}
|
||||||
|
bookId={id}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<OrganizePreviewModalConnector
|
<OrganizePreviewModalConnector
|
||||||
isOpen={isOrganizeModalOpen}
|
isOpen={isOrganizeModalOpen}
|
||||||
artistId={artist.id}
|
authorId={artist.id}
|
||||||
albumId={id}
|
bookId={id}
|
||||||
onModalClose={this.onOrganizeModalClose}
|
onModalClose={this.onOrganizeModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<RetagPreviewModalConnector
|
{/* <RetagPreviewModalConnector */}
|
||||||
isOpen={isRetagModalOpen}
|
{/* isOpen={isRetagModalOpen} */}
|
||||||
artistId={artist.id}
|
{/* authorId={artist.id} */}
|
||||||
albumId={id}
|
{/* bookId={id} */}
|
||||||
onModalClose={this.onRetagModalClose}
|
{/* onModalClose={this.onRetagModalClose} */}
|
||||||
/>
|
{/* /> */}
|
||||||
|
|
||||||
<TrackFileEditorModal
|
|
||||||
isOpen={isManageTracksOpen}
|
|
||||||
artistId={artist.id}
|
|
||||||
albumId={id}
|
|
||||||
onModalClose={this.onManageTracksModalClose}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<AlbumInteractiveSearchModalConnector
|
|
||||||
isOpen={isInteractiveSearchModalOpen}
|
|
||||||
albumId={id}
|
|
||||||
albumTitle={title}
|
|
||||||
onModalClose={this.onInteractiveSearchModalClose}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ArtistHistoryModal
|
|
||||||
isOpen={isArtistHistoryModalOpen}
|
|
||||||
artistId={artist.id}
|
|
||||||
albumId={id}
|
|
||||||
onModalClose={this.onArtistHistoryModalClose}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<EditAlbumModalConnector
|
|
||||||
isOpen={isEditAlbumModalOpen}
|
|
||||||
albumId={id}
|
|
||||||
artistId={artist.id}
|
|
||||||
onModalClose={this.onEditAlbumModalClose}
|
|
||||||
onDeleteArtistPress={this.onDeleteAlbumPress}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DeleteAlbumModal
|
<DeleteAlbumModal
|
||||||
isOpen={isDeleteAlbumModalOpen}
|
isOpen={isDeleteAlbumModalOpen}
|
||||||
albumId={id}
|
bookId={id}
|
||||||
foreignArtistId={artist.foreignArtistId}
|
titleSlug={artist.titleSlug}
|
||||||
onModalClose={this.onDeleteAlbumModalClose}
|
onModalClose={this.onDeleteAlbumModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -587,26 +503,22 @@ class AlbumDetails extends Component {
|
|||||||
|
|
||||||
AlbumDetails.propTypes = {
|
AlbumDetails.propTypes = {
|
||||||
id: PropTypes.number.isRequired,
|
id: PropTypes.number.isRequired,
|
||||||
foreignAlbumId: PropTypes.string.isRequired,
|
titleSlug: PropTypes.string.isRequired,
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
disambiguation: PropTypes.string,
|
disambiguation: PropTypes.string,
|
||||||
duration: PropTypes.number,
|
duration: PropTypes.number,
|
||||||
overview: PropTypes.string,
|
overview: PropTypes.string,
|
||||||
albumType: PropTypes.string.isRequired,
|
|
||||||
statistics: PropTypes.object.isRequired,
|
statistics: PropTypes.object.isRequired,
|
||||||
releaseDate: PropTypes.string.isRequired,
|
releaseDate: PropTypes.string.isRequired,
|
||||||
ratings: PropTypes.object.isRequired,
|
ratings: PropTypes.object.isRequired,
|
||||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
links: PropTypes.arrayOf(PropTypes.object).isRequired,
|
links: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
media: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
monitored: PropTypes.bool.isRequired,
|
monitored: PropTypes.bool.isRequired,
|
||||||
shortDateFormat: PropTypes.string.isRequired,
|
shortDateFormat: PropTypes.string.isRequired,
|
||||||
isSaving: PropTypes.bool.isRequired,
|
isSaving: PropTypes.bool.isRequired,
|
||||||
isSearching: PropTypes.bool,
|
isSearching: PropTypes.bool,
|
||||||
isFetching: PropTypes.bool,
|
isFetching: PropTypes.bool,
|
||||||
isPopulated: PropTypes.bool,
|
isPopulated: PropTypes.bool,
|
||||||
albumsError: PropTypes.object,
|
|
||||||
tracksError: PropTypes.object,
|
|
||||||
trackFilesError: PropTypes.object,
|
trackFilesError: PropTypes.object,
|
||||||
hasTrackFiles: PropTypes.bool.isRequired,
|
hasTrackFiles: PropTypes.bool.isRequired,
|
||||||
artist: PropTypes.object,
|
artist: PropTypes.object,
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import { findCommand, isCommandExecuting } from 'Utilities/Command';
|
|||||||
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
||||||
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
|
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
|
||||||
import { toggleAlbumsMonitored } from 'Store/Actions/albumActions';
|
import { toggleAlbumsMonitored } from 'Store/Actions/albumActions';
|
||||||
import { fetchTracks, clearTracks } from 'Store/Actions/trackActions';
|
|
||||||
import { fetchTrackFiles, clearTrackFiles } from 'Store/Actions/trackFileActions';
|
import { fetchTrackFiles, clearTrackFiles } from 'Store/Actions/trackFileActions';
|
||||||
|
import { clearReleases, cancelFetchReleases } from 'Store/Actions/releaseActions';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
import * as commandNames from 'Commands/commandNames';
|
import * as commandNames from 'Commands/commandNames';
|
||||||
import AlbumDetails from './AlbumDetails';
|
import AlbumDetails from './AlbumDetails';
|
||||||
@@ -39,18 +39,17 @@ const selectTrackFiles = createSelector(
|
|||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state, { foreignAlbumId }) => foreignAlbumId,
|
(state, { titleSlug }) => titleSlug,
|
||||||
(state) => state.tracks,
|
|
||||||
selectTrackFiles,
|
selectTrackFiles,
|
||||||
(state) => state.albums,
|
(state) => state.albums,
|
||||||
createAllArtistSelector(),
|
createAllArtistSelector(),
|
||||||
createCommandsSelector(),
|
createCommandsSelector(),
|
||||||
createUISettingsSelector(),
|
createUISettingsSelector(),
|
||||||
(foreignAlbumId, tracks, trackFiles, albums, artists, commands, uiSettings) => {
|
(titleSlug, trackFiles, albums, artists, commands, uiSettings) => {
|
||||||
const sortedAlbums = _.orderBy(albums.items, 'releaseDate');
|
const sortedAlbums = _.orderBy(albums.items, 'releaseDate');
|
||||||
const albumIndex = _.findIndex(sortedAlbums, { foreignAlbumId });
|
const albumIndex = _.findIndex(sortedAlbums, { titleSlug });
|
||||||
const album = sortedAlbums[albumIndex];
|
const album = sortedAlbums[albumIndex];
|
||||||
const artist = _.find(artists, { id: album.artistId });
|
const artist = _.find(artists, { id: album.authorId });
|
||||||
|
|
||||||
if (!album) {
|
if (!album) {
|
||||||
return {};
|
return {};
|
||||||
@@ -68,12 +67,11 @@ function createMapStateToProps() {
|
|||||||
const isSearchingCommand = findCommand(commands, { name: commandNames.ALBUM_SEARCH });
|
const isSearchingCommand = findCommand(commands, { name: commandNames.ALBUM_SEARCH });
|
||||||
const isSearching = (
|
const isSearching = (
|
||||||
isCommandExecuting(isSearchingCommand) &&
|
isCommandExecuting(isSearchingCommand) &&
|
||||||
isSearchingCommand.body.albumIds.indexOf(album.id) > -1
|
isSearchingCommand.body.bookIds.indexOf(album.id) > -1
|
||||||
);
|
);
|
||||||
|
|
||||||
const isFetching = tracks.isFetching || isTrackFilesFetching;
|
const isFetching = isTrackFilesFetching;
|
||||||
const isPopulated = tracks.isPopulated && isTrackFilesPopulated;
|
const isPopulated = isTrackFilesPopulated;
|
||||||
const tracksError = tracks.error;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...album,
|
...album,
|
||||||
@@ -82,7 +80,6 @@ function createMapStateToProps() {
|
|||||||
isSearching,
|
isSearching,
|
||||||
isFetching,
|
isFetching,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
tracksError,
|
|
||||||
trackFilesError,
|
trackFilesError,
|
||||||
hasTrackFiles,
|
hasTrackFiles,
|
||||||
previousAlbum,
|
previousAlbum,
|
||||||
@@ -94,17 +91,13 @@ function createMapStateToProps() {
|
|||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
executeCommand,
|
executeCommand,
|
||||||
fetchTracks,
|
|
||||||
clearTracks,
|
|
||||||
fetchTrackFiles,
|
fetchTrackFiles,
|
||||||
clearTrackFiles,
|
clearTrackFiles,
|
||||||
|
clearReleases,
|
||||||
|
cancelFetchReleases,
|
||||||
toggleAlbumsMonitored
|
toggleAlbumsMonitored
|
||||||
};
|
};
|
||||||
|
|
||||||
function getMonitoredReleases(props) {
|
|
||||||
return _.map(_.filter(props.releases, { monitored: true }), 'id').sort();
|
|
||||||
}
|
|
||||||
|
|
||||||
class AlbumDetailsConnector extends Component {
|
class AlbumDetailsConnector extends Component {
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -113,8 +106,10 @@ class AlbumDetailsConnector extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (!_.isEqual(getMonitoredReleases(prevProps), getMonitoredReleases(this.props)) ||
|
// If the id has changed we need to clear the albums
|
||||||
(prevProps.anyReleaseOk === false && this.props.anyReleaseOk === true)) {
|
// files and fetch from the server.
|
||||||
|
|
||||||
|
if (prevProps.id !== this.props.id) {
|
||||||
this.unpopulate();
|
this.unpopulate();
|
||||||
this.populate();
|
this.populate();
|
||||||
}
|
}
|
||||||
@@ -129,14 +124,14 @@ class AlbumDetailsConnector extends Component {
|
|||||||
// Control
|
// Control
|
||||||
|
|
||||||
populate = () => {
|
populate = () => {
|
||||||
const albumId = this.props.id;
|
const bookId = this.props.id;
|
||||||
|
|
||||||
this.props.fetchTracks({ albumId });
|
this.props.fetchTrackFiles({ bookId });
|
||||||
this.props.fetchTrackFiles({ albumId });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unpopulate = () => {
|
unpopulate = () => {
|
||||||
this.props.clearTracks();
|
this.props.cancelFetchReleases();
|
||||||
|
this.props.clearReleases();
|
||||||
this.props.clearTrackFiles();
|
this.props.clearTrackFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +140,7 @@ class AlbumDetailsConnector extends Component {
|
|||||||
|
|
||||||
onMonitorTogglePress = (monitored) => {
|
onMonitorTogglePress = (monitored) => {
|
||||||
this.props.toggleAlbumsMonitored({
|
this.props.toggleAlbumsMonitored({
|
||||||
albumIds: [this.props.id],
|
bookIds: [this.props.id],
|
||||||
monitored
|
monitored
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -153,7 +148,7 @@ class AlbumDetailsConnector extends Component {
|
|||||||
onSearchPress = () => {
|
onSearchPress = () => {
|
||||||
this.props.executeCommand({
|
this.props.executeCommand({
|
||||||
name: commandNames.ALBUM_SEARCH,
|
name: commandNames.ALBUM_SEARCH,
|
||||||
albumIds: [this.props.id]
|
bookIds: [this.props.id]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,11 +171,11 @@ AlbumDetailsConnector.propTypes = {
|
|||||||
anyReleaseOk: PropTypes.bool,
|
anyReleaseOk: PropTypes.bool,
|
||||||
isAlbumFetching: PropTypes.bool,
|
isAlbumFetching: PropTypes.bool,
|
||||||
isAlbumPopulated: PropTypes.bool,
|
isAlbumPopulated: PropTypes.bool,
|
||||||
foreignAlbumId: PropTypes.string.isRequired,
|
titleSlug: PropTypes.string.isRequired,
|
||||||
fetchTracks: PropTypes.func.isRequired,
|
|
||||||
clearTracks: PropTypes.func.isRequired,
|
|
||||||
fetchTrackFiles: PropTypes.func.isRequired,
|
fetchTrackFiles: PropTypes.func.isRequired,
|
||||||
clearTrackFiles: PropTypes.func.isRequired,
|
clearTrackFiles: PropTypes.func.isRequired,
|
||||||
|
clearReleases: PropTypes.func.isRequired,
|
||||||
|
cancelFetchReleases: PropTypes.func.isRequired,
|
||||||
toggleAlbumsMonitored: PropTypes.func.isRequired,
|
toggleAlbumsMonitored: PropTypes.func.isRequired,
|
||||||
executeCommand: PropTypes.func.isRequired
|
executeCommand: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,26 +7,12 @@ import styles from './AlbumDetailsLinks.css';
|
|||||||
|
|
||||||
function AlbumDetailsLinks(props) {
|
function AlbumDetailsLinks(props) {
|
||||||
const {
|
const {
|
||||||
foreignAlbumId,
|
|
||||||
links
|
links
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.links}>
|
<div className={styles.links}>
|
||||||
|
|
||||||
<Link
|
|
||||||
className={styles.link}
|
|
||||||
to={`https://musicbrainz.org/release-group/${foreignAlbumId}`}
|
|
||||||
>
|
|
||||||
<Label
|
|
||||||
className={styles.linkLabel}
|
|
||||||
kind={kinds.INFO}
|
|
||||||
size={sizes.LARGE}
|
|
||||||
>
|
|
||||||
Musicbrainz
|
|
||||||
</Label>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
{links.map((link, index) => {
|
{links.map((link, index) => {
|
||||||
return (
|
return (
|
||||||
<span key={index}>
|
<span key={index}>
|
||||||
@@ -56,7 +42,6 @@ function AlbumDetailsLinks(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AlbumDetailsLinks.propTypes = {
|
AlbumDetailsLinks.propTypes = {
|
||||||
foreignAlbumId: PropTypes.string.isRequired,
|
|
||||||
links: PropTypes.arrayOf(PropTypes.object).isRequired
|
links: PropTypes.arrayOf(PropTypes.object).isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,210 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { icons, kinds, sizes } from 'Helpers/Props';
|
|
||||||
import Icon from 'Components/Icon';
|
|
||||||
import IconButton from 'Components/Link/IconButton';
|
|
||||||
import Label from 'Components/Label';
|
|
||||||
import Link from 'Components/Link/Link';
|
|
||||||
import Table from 'Components/Table/Table';
|
|
||||||
import TableBody from 'Components/Table/TableBody';
|
|
||||||
import TrackRowConnector from './TrackRowConnector';
|
|
||||||
import styles from './AlbumDetailsMedium.css';
|
|
||||||
|
|
||||||
function getMediumStatistics(tracks) {
|
|
||||||
let trackCount = 0;
|
|
||||||
let trackFileCount = 0;
|
|
||||||
let totalTrackCount = 0;
|
|
||||||
|
|
||||||
tracks.forEach((track) => {
|
|
||||||
if (track.trackFileId) {
|
|
||||||
trackCount++;
|
|
||||||
trackFileCount++;
|
|
||||||
} else {
|
|
||||||
trackCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
totalTrackCount++;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
trackCount,
|
|
||||||
trackFileCount,
|
|
||||||
totalTrackCount
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTrackCountKind(monitored, trackFileCount, trackCount) {
|
|
||||||
if (trackFileCount === trackCount && trackCount > 0) {
|
|
||||||
return kinds.SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!monitored) {
|
|
||||||
return kinds.WARNING;
|
|
||||||
}
|
|
||||||
|
|
||||||
return kinds.DANGER;
|
|
||||||
}
|
|
||||||
|
|
||||||
class AlbumDetailsMedium extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this._expandByDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
|
||||||
if (prevProps.albumId !== this.props.albumId) {
|
|
||||||
this._expandByDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Control
|
|
||||||
|
|
||||||
_expandByDefault() {
|
|
||||||
const {
|
|
||||||
mediumNumber,
|
|
||||||
onExpandPress
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
onExpandPress(mediumNumber, mediumNumber === 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onExpandPress = () => {
|
|
||||||
const {
|
|
||||||
mediumNumber,
|
|
||||||
isExpanded
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
this.props.onExpandPress(mediumNumber, !isExpanded);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
mediumNumber,
|
|
||||||
mediumFormat,
|
|
||||||
albumMonitored,
|
|
||||||
items,
|
|
||||||
columns,
|
|
||||||
onTableOptionChange,
|
|
||||||
isExpanded,
|
|
||||||
isSmallScreen
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const {
|
|
||||||
trackCount,
|
|
||||||
trackFileCount,
|
|
||||||
totalTrackCount
|
|
||||||
} = getMediumStatistics(items);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={styles.medium}
|
|
||||||
>
|
|
||||||
<div className={styles.header}>
|
|
||||||
<div className={styles.left}>
|
|
||||||
{
|
|
||||||
<div>
|
|
||||||
<span className={styles.mediumNumber}>
|
|
||||||
{mediumFormat} {mediumNumber}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<Label
|
|
||||||
title={`${totalTrackCount} tracks total. ${trackFileCount} tracks with files.`}
|
|
||||||
kind={getTrackCountKind(albumMonitored, trackFileCount, trackCount)}
|
|
||||||
size={sizes.LARGE}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
<span>{trackFileCount} / {trackCount}</span>
|
|
||||||
}
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Link
|
|
||||||
className={styles.expandButton}
|
|
||||||
onPress={this.onExpandPress}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
className={styles.expandButtonIcon}
|
|
||||||
name={isExpanded ? icons.COLLAPSE : icons.EXPAND}
|
|
||||||
title={isExpanded ? 'Hide tracks' : 'Show tracks'}
|
|
||||||
size={24}
|
|
||||||
/>
|
|
||||||
{
|
|
||||||
!isSmallScreen &&
|
|
||||||
<span> </span>
|
|
||||||
}
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
{
|
|
||||||
isExpanded &&
|
|
||||||
<div className={styles.tracks}>
|
|
||||||
{
|
|
||||||
items.length ?
|
|
||||||
<Table
|
|
||||||
columns={columns}
|
|
||||||
onTableOptionChange={onTableOptionChange}
|
|
||||||
>
|
|
||||||
<TableBody>
|
|
||||||
{
|
|
||||||
items.map((item) => {
|
|
||||||
return (
|
|
||||||
<TrackRowConnector
|
|
||||||
key={item.id}
|
|
||||||
columns={columns}
|
|
||||||
{...item}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</TableBody>
|
|
||||||
</Table> :
|
|
||||||
|
|
||||||
<div className={styles.noTracks}>
|
|
||||||
No tracks in this medium
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<div className={styles.collapseButtonContainer}>
|
|
||||||
<IconButton
|
|
||||||
name={icons.COLLAPSE}
|
|
||||||
size={20}
|
|
||||||
title="Hide tracks"
|
|
||||||
onPress={this.onExpandPress}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AlbumDetailsMedium.propTypes = {
|
|
||||||
albumId: PropTypes.number.isRequired,
|
|
||||||
albumMonitored: PropTypes.bool.isRequired,
|
|
||||||
mediumNumber: PropTypes.number.isRequired,
|
|
||||||
mediumFormat: PropTypes.string.isRequired,
|
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
isSaving: PropTypes.bool,
|
|
||||||
isExpanded: PropTypes.bool,
|
|
||||||
isSmallScreen: PropTypes.bool.isRequired,
|
|
||||||
onTableOptionChange: PropTypes.func.isRequired,
|
|
||||||
onExpandPress: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AlbumDetailsMedium;
|
|
||||||
@@ -1,65 +0,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 { setTracksTableOption } from 'Store/Actions/trackActions';
|
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
|
||||||
import AlbumDetailsMedium from './AlbumDetailsMedium';
|
|
||||||
|
|
||||||
function createMapStateToProps() {
|
|
||||||
return createSelector(
|
|
||||||
(state, { mediumNumber }) => mediumNumber,
|
|
||||||
(state) => state.tracks,
|
|
||||||
createDimensionsSelector(),
|
|
||||||
(mediumNumber, tracks, dimensions) => {
|
|
||||||
|
|
||||||
const tracksInMedium = _.filter(tracks.items, { mediumNumber });
|
|
||||||
const sortedTracks = _.orderBy(tracksInMedium, ['absoluteTrackNumber'], ['asc']);
|
|
||||||
|
|
||||||
return {
|
|
||||||
items: sortedTracks,
|
|
||||||
columns: tracks.columns,
|
|
||||||
isSmallScreen: dimensions.isSmallScreen
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
|
||||||
setTracksTableOption,
|
|
||||||
executeCommand
|
|
||||||
};
|
|
||||||
|
|
||||||
class AlbumDetailsMediumConnector extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onTableOptionChange = (payload) => {
|
|
||||||
this.props.setTracksTableOption(payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<AlbumDetailsMedium
|
|
||||||
{...this.props}
|
|
||||||
onTableOptionChange={this.onTableOptionChange}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AlbumDetailsMediumConnector.propTypes = {
|
|
||||||
albumId: PropTypes.number.isRequired,
|
|
||||||
albumMonitored: PropTypes.bool.isRequired,
|
|
||||||
mediumNumber: PropTypes.number.isRequired,
|
|
||||||
setTracksTableOption: PropTypes.func.isRequired,
|
|
||||||
executeCommand: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps)(AlbumDetailsMediumConnector);
|
|
||||||
@@ -17,14 +17,14 @@ function createMapStateToProps() {
|
|||||||
(state) => state.albums,
|
(state) => state.albums,
|
||||||
(state) => state.artist,
|
(state) => state.artist,
|
||||||
(match, albums, artist) => {
|
(match, albums, artist) => {
|
||||||
const foreignAlbumId = match.params.foreignAlbumId;
|
const titleSlug = match.params.titleSlug;
|
||||||
const isFetching = albums.isFetching || artist.isFetching;
|
const isFetching = albums.isFetching || artist.isFetching;
|
||||||
const isPopulated = albums.isPopulated && artist.isPopulated;
|
const isPopulated = albums.isPopulated && artist.isPopulated;
|
||||||
|
|
||||||
// if albums have been fetched, make sure requested one exists
|
// if albums have been fetched, make sure requested one exists
|
||||||
// otherwise don't map foreignAlbumId to trigger not found page
|
// otherwise don't map titleSlug to trigger not found page
|
||||||
if (!isFetching && isPopulated) {
|
if (!isFetching && isPopulated) {
|
||||||
const albumIndex = _.findIndex(albums.items, { foreignAlbumId });
|
const albumIndex = _.findIndex(albums.items, { titleSlug });
|
||||||
if (albumIndex === -1) {
|
if (albumIndex === -1) {
|
||||||
return {
|
return {
|
||||||
isFetching,
|
isFetching,
|
||||||
@@ -34,7 +34,7 @@ function createMapStateToProps() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
foreignAlbumId,
|
titleSlug,
|
||||||
isFetching,
|
isFetching,
|
||||||
isPopulated
|
isPopulated
|
||||||
};
|
};
|
||||||
@@ -69,10 +69,10 @@ class AlbumDetailsPageConnector extends Component {
|
|||||||
// Control
|
// Control
|
||||||
|
|
||||||
populate = () => {
|
populate = () => {
|
||||||
const foreignAlbumId = this.props.foreignAlbumId;
|
const titleSlug = this.props.titleSlug;
|
||||||
this.setState({ hasMounted: true });
|
this.setState({ hasMounted: true });
|
||||||
this.props.fetchAlbums({
|
this.props.fetchAlbums({
|
||||||
foreignAlbumId,
|
titleSlug,
|
||||||
includeAllArtistAlbums: true
|
includeAllArtistAlbums: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -86,15 +86,15 @@ class AlbumDetailsPageConnector extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
foreignAlbumId,
|
titleSlug,
|
||||||
isFetching,
|
isFetching,
|
||||||
isPopulated
|
isPopulated
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (!foreignAlbumId) {
|
if (!titleSlug) {
|
||||||
return (
|
return (
|
||||||
<NotFound
|
<NotFound
|
||||||
message="Sorry, that album cannot be found."
|
message="Sorry, that book cannot be found."
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ class AlbumDetailsPageConnector extends Component {
|
|||||||
if (!isFetching && isPopulated && this.state.hasMounted) {
|
if (!isFetching && isPopulated && this.state.hasMounted) {
|
||||||
return (
|
return (
|
||||||
<AlbumDetailsConnector
|
<AlbumDetailsConnector
|
||||||
foreignAlbumId={foreignAlbumId}
|
titleSlug={titleSlug}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -121,8 +121,8 @@ class AlbumDetailsPageConnector extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AlbumDetailsPageConnector.propTypes = {
|
AlbumDetailsPageConnector.propTypes = {
|
||||||
foreignAlbumId: PropTypes.string,
|
titleSlug: PropTypes.string,
|
||||||
match: PropTypes.shape({ params: PropTypes.shape({ foreignAlbumId: PropTypes.string.isRequired }).isRequired }).isRequired,
|
match: PropTypes.shape({ params: PropTypes.shape({ titleSlug: PropTypes.string.isRequired }).isRequired }).isRequired,
|
||||||
push: PropTypes.func.isRequired,
|
push: PropTypes.func.isRequired,
|
||||||
fetchAlbums: PropTypes.func.isRequired,
|
fetchAlbums: PropTypes.func.isRequired,
|
||||||
clearAlbums: PropTypes.func.isRequired,
|
clearAlbums: PropTypes.func.isRequired,
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
.TrackActionsCell {
|
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
|
||||||
|
|
||||||
width: 70px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { icons, kinds } from 'Helpers/Props';
|
|
||||||
import IconButton from 'Components/Link/IconButton';
|
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
|
||||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
|
||||||
import FileDetailsModal from 'TrackFile/FileDetailsModal';
|
|
||||||
import styles from './TrackActionsCell.css';
|
|
||||||
|
|
||||||
class TrackActionsCell extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
isDetailsModalOpen: false,
|
|
||||||
isConfirmDeleteModalOpen: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onDetailsPress = () => {
|
|
||||||
this.setState({ isDetailsModalOpen: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
onDetailsModalClose = () => {
|
|
||||||
this.setState({ isDetailsModalOpen: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
onDeleteFilePress = () => {
|
|
||||||
this.setState({ isConfirmDeleteModalOpen: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
onConfirmDelete = () => {
|
|
||||||
this.setState({ isConfirmDeleteModalOpen: false });
|
|
||||||
this.props.deleteTrackFile({ id: this.props.trackFileId });
|
|
||||||
}
|
|
||||||
|
|
||||||
onConfirmDeleteModalClose = () => {
|
|
||||||
this.setState({ isConfirmDeleteModalOpen: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
|
|
||||||
const {
|
|
||||||
trackFileId,
|
|
||||||
trackFilePath
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const {
|
|
||||||
isDetailsModalOpen,
|
|
||||||
isConfirmDeleteModalOpen
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableRowCell className={styles.TrackActionsCell}>
|
|
||||||
{
|
|
||||||
trackFilePath &&
|
|
||||||
<IconButton
|
|
||||||
name={icons.INFO}
|
|
||||||
onPress={this.onDetailsPress}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
{
|
|
||||||
trackFilePath &&
|
|
||||||
<IconButton
|
|
||||||
name={icons.DELETE}
|
|
||||||
onPress={this.onDeleteFilePress}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
|
|
||||||
<FileDetailsModal
|
|
||||||
isOpen={isDetailsModalOpen}
|
|
||||||
onModalClose={this.onDetailsModalClose}
|
|
||||||
id={trackFileId}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ConfirmModal
|
|
||||||
isOpen={isConfirmDeleteModalOpen}
|
|
||||||
kind={kinds.DANGER}
|
|
||||||
title="Delete Track File"
|
|
||||||
message={`Are you sure you want to delete ${trackFilePath}?`}
|
|
||||||
confirmLabel="Delete"
|
|
||||||
onConfirm={this.onConfirmDelete}
|
|
||||||
onCancel={this.onConfirmDeleteModalClose}
|
|
||||||
/>
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TrackActionsCell.propTypes = {
|
|
||||||
id: PropTypes.number.isRequired,
|
|
||||||
albumId: PropTypes.number.isRequired,
|
|
||||||
trackFilePath: PropTypes.string,
|
|
||||||
trackFileId: PropTypes.number.isRequired,
|
|
||||||
deleteTrackFile: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TrackActionsCell;
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
.title {
|
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
|
||||||
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.monitored {
|
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
|
||||||
|
|
||||||
width: 42px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trackNumber {
|
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
|
||||||
|
|
||||||
width: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.audio {
|
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
|
||||||
|
|
||||||
width: 250px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.duration,
|
|
||||||
.status {
|
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
|
||||||
|
|
||||||
width: 100px;
|
|
||||||
}
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import TableRow from 'Components/Table/TableRow';
|
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
|
||||||
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
|
|
||||||
import EpisodeStatusConnector from 'Album/EpisodeStatusConnector';
|
|
||||||
import MediaInfoConnector from 'TrackFile/MediaInfoConnector';
|
|
||||||
import TrackActionsCell from './TrackActionsCell';
|
|
||||||
import * as mediaInfoTypes from 'TrackFile/mediaInfoTypes';
|
|
||||||
|
|
||||||
import styles from './TrackRow.css';
|
|
||||||
|
|
||||||
class TrackRow extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
albumId,
|
|
||||||
mediumNumber,
|
|
||||||
trackFileId,
|
|
||||||
absoluteTrackNumber,
|
|
||||||
title,
|
|
||||||
duration,
|
|
||||||
trackFilePath,
|
|
||||||
columns,
|
|
||||||
deleteTrackFile
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableRow>
|
|
||||||
{
|
|
||||||
columns.map((column) => {
|
|
||||||
const {
|
|
||||||
name,
|
|
||||||
isVisible
|
|
||||||
} = column;
|
|
||||||
|
|
||||||
if (!isVisible) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'medium') {
|
|
||||||
return (
|
|
||||||
<TableRowCell
|
|
||||||
key={name}
|
|
||||||
className={styles.trackNumber}
|
|
||||||
>
|
|
||||||
{mediumNumber}
|
|
||||||
</TableRowCell>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'absoluteTrackNumber') {
|
|
||||||
return (
|
|
||||||
<TableRowCell
|
|
||||||
key={name}
|
|
||||||
className={styles.trackNumber}
|
|
||||||
>
|
|
||||||
{absoluteTrackNumber}
|
|
||||||
</TableRowCell>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'title') {
|
|
||||||
return (
|
|
||||||
<TableRowCell
|
|
||||||
key={name}
|
|
||||||
className={styles.title}
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</TableRowCell>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'path') {
|
|
||||||
return (
|
|
||||||
<TableRowCell key={name}>
|
|
||||||
{
|
|
||||||
trackFilePath
|
|
||||||
}
|
|
||||||
</TableRowCell>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'duration') {
|
|
||||||
return (
|
|
||||||
<TableRowCell
|
|
||||||
key={name}
|
|
||||||
className={styles.duration}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
formatTimeSpan(duration)
|
|
||||||
}
|
|
||||||
</TableRowCell>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'audioInfo') {
|
|
||||||
return (
|
|
||||||
<TableRowCell
|
|
||||||
key={name}
|
|
||||||
className={styles.audio}
|
|
||||||
>
|
|
||||||
<MediaInfoConnector
|
|
||||||
type={mediaInfoTypes.AUDIO}
|
|
||||||
trackFileId={trackFileId}
|
|
||||||
/>
|
|
||||||
</TableRowCell>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'status') {
|
|
||||||
return (
|
|
||||||
<TableRowCell
|
|
||||||
key={name}
|
|
||||||
className={styles.status}
|
|
||||||
>
|
|
||||||
<EpisodeStatusConnector
|
|
||||||
albumId={id}
|
|
||||||
trackFileId={trackFileId}
|
|
||||||
/>
|
|
||||||
</TableRowCell>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'actions') {
|
|
||||||
return (
|
|
||||||
<TrackActionsCell
|
|
||||||
key={name}
|
|
||||||
albumId={albumId}
|
|
||||||
id={id}
|
|
||||||
trackFilePath={trackFilePath}
|
|
||||||
trackFileId={trackFileId}
|
|
||||||
deleteTrackFile={deleteTrackFile}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</TableRow>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TrackRow.propTypes = {
|
|
||||||
deleteTrackFile: PropTypes.func.isRequired,
|
|
||||||
id: PropTypes.number.isRequired,
|
|
||||||
albumId: PropTypes.number.isRequired,
|
|
||||||
trackFileId: PropTypes.number,
|
|
||||||
mediumNumber: PropTypes.number.isRequired,
|
|
||||||
trackNumber: PropTypes.string.isRequired,
|
|
||||||
absoluteTrackNumber: PropTypes.number,
|
|
||||||
title: PropTypes.string.isRequired,
|
|
||||||
duration: PropTypes.number.isRequired,
|
|
||||||
isSaving: PropTypes.bool,
|
|
||||||
trackFilePath: PropTypes.string,
|
|
||||||
mediaInfo: PropTypes.object,
|
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TrackRow;
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { connect } from 'react-redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import createTrackFileSelector from 'Store/Selectors/createTrackFileSelector';
|
|
||||||
import { deleteTrackFile } from 'Store/Actions/trackFileActions';
|
|
||||||
import TrackRow from './TrackRow';
|
|
||||||
|
|
||||||
function createMapStateToProps() {
|
|
||||||
return createSelector(
|
|
||||||
(state, { id }) => id,
|
|
||||||
createTrackFileSelector(),
|
|
||||||
(id, trackFile) => {
|
|
||||||
return {
|
|
||||||
trackFilePath: trackFile ? trackFile.path : null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
|
||||||
deleteTrackFile
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps)(TrackRow);
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import Modal from 'Components/Modal/Modal';
|
|
||||||
import EditAlbumModalContentConnector from './EditAlbumModalContentConnector';
|
|
||||||
|
|
||||||
function EditAlbumModal({ isOpen, onModalClose, ...otherProps }) {
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
isOpen={isOpen}
|
|
||||||
onModalClose={onModalClose}
|
|
||||||
>
|
|
||||||
<EditAlbumModalContentConnector
|
|
||||||
{...otherProps}
|
|
||||||
onModalClose={onModalClose}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
EditAlbumModal.propTypes = {
|
|
||||||
isOpen: PropTypes.bool.isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EditAlbumModal;
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
|
||||||
import EditAlbumModal from './EditAlbumModal';
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
|
||||||
clearPendingChanges
|
|
||||||
};
|
|
||||||
|
|
||||||
class EditAlbumModalConnector extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onModalClose = () => {
|
|
||||||
this.props.clearPendingChanges({ section: 'albums' });
|
|
||||||
this.props.onModalClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<EditAlbumModal
|
|
||||||
{...this.props}
|
|
||||||
onModalClose={this.onModalClose}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
EditAlbumModalConnector.propTypes = {
|
|
||||||
onModalClose: PropTypes.func.isRequired,
|
|
||||||
clearPendingChanges: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(undefined, mapDispatchToProps)(EditAlbumModalConnector);
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { inputTypes } from 'Helpers/Props';
|
|
||||||
import Button from 'Components/Link/Button';
|
|
||||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
|
||||||
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 Form from 'Components/Form/Form';
|
|
||||||
import FormGroup from 'Components/Form/FormGroup';
|
|
||||||
import FormLabel from 'Components/Form/FormLabel';
|
|
||||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
|
||||||
|
|
||||||
class EditAlbumModalContent extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onSavePress = () => {
|
|
||||||
const {
|
|
||||||
onSavePress
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
onSavePress(false);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
title,
|
|
||||||
artistName,
|
|
||||||
albumType,
|
|
||||||
statistics,
|
|
||||||
item,
|
|
||||||
isSaving,
|
|
||||||
onInputChange,
|
|
||||||
onModalClose,
|
|
||||||
...otherProps
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const {
|
|
||||||
monitored,
|
|
||||||
anyReleaseOk,
|
|
||||||
releases
|
|
||||||
} = item;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ModalContent onModalClose={onModalClose}>
|
|
||||||
<ModalHeader>
|
|
||||||
Edit - {artistName} - {title} [{albumType}]
|
|
||||||
</ModalHeader>
|
|
||||||
|
|
||||||
<ModalBody>
|
|
||||||
<Form
|
|
||||||
{...otherProps}
|
|
||||||
>
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>Monitored</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.CHECK}
|
|
||||||
name="monitored"
|
|
||||||
helpText="Readarr will search for and download album"
|
|
||||||
{...monitored}
|
|
||||||
onChange={onInputChange}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>Automatically Switch Release</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.CHECK}
|
|
||||||
name="anyReleaseOk"
|
|
||||||
helpText="Readarr will automatically switch to the release best matching downloaded tracks"
|
|
||||||
{...anyReleaseOk}
|
|
||||||
onChange={onInputChange}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel> Release</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.ALBUM_RELEASE_SELECT}
|
|
||||||
name="releases"
|
|
||||||
helpText="Change release for this album"
|
|
||||||
isDisabled={anyReleaseOk.value && statistics.trackFileCount > 0}
|
|
||||||
albumReleases={releases}
|
|
||||||
onChange={onInputChange}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
</Form>
|
|
||||||
</ModalBody>
|
|
||||||
<ModalFooter>
|
|
||||||
<Button
|
|
||||||
onPress={onModalClose}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<SpinnerButton
|
|
||||||
isSpinning={isSaving}
|
|
||||||
onPress={this.onSavePress}
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</SpinnerButton>
|
|
||||||
</ModalFooter>
|
|
||||||
|
|
||||||
</ModalContent>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
EditAlbumModalContent.propTypes = {
|
|
||||||
albumId: PropTypes.number.isRequired,
|
|
||||||
title: PropTypes.string.isRequired,
|
|
||||||
artistName: PropTypes.string.isRequired,
|
|
||||||
albumType: PropTypes.string.isRequired,
|
|
||||||
statistics: PropTypes.object.isRequired,
|
|
||||||
item: PropTypes.object.isRequired,
|
|
||||||
isSaving: PropTypes.bool.isRequired,
|
|
||||||
onInputChange: PropTypes.func.isRequired,
|
|
||||||
onSavePress: PropTypes.func.isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EditAlbumModalContent;
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import selectSettings from 'Store/Selectors/selectSettings';
|
|
||||||
import createAlbumSelector from 'Store/Selectors/createAlbumSelector';
|
|
||||||
import createArtistSelector from 'Store/Selectors/createArtistSelector';
|
|
||||||
import { setAlbumValue, saveAlbum } from 'Store/Actions/albumActions';
|
|
||||||
import EditAlbumModalContent from './EditAlbumModalContent';
|
|
||||||
|
|
||||||
function createMapStateToProps() {
|
|
||||||
return createSelector(
|
|
||||||
(state) => state.albums,
|
|
||||||
createAlbumSelector(),
|
|
||||||
createArtistSelector(),
|
|
||||||
(albumState, album, artist) => {
|
|
||||||
const {
|
|
||||||
isSaving,
|
|
||||||
saveError,
|
|
||||||
pendingChanges
|
|
||||||
} = albumState;
|
|
||||||
|
|
||||||
const albumSettings = _.pick(album, [
|
|
||||||
'monitored',
|
|
||||||
'anyReleaseOk',
|
|
||||||
'releases'
|
|
||||||
]);
|
|
||||||
|
|
||||||
const settings = selectSettings(albumSettings, pendingChanges, saveError);
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: album.title,
|
|
||||||
artistName: artist.artistName,
|
|
||||||
albumType: album.albumType,
|
|
||||||
statistics: album.statistics,
|
|
||||||
isSaving,
|
|
||||||
saveError,
|
|
||||||
item: settings.settings,
|
|
||||||
...settings
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
|
||||||
dispatchSetAlbumValue: setAlbumValue,
|
|
||||||
dispatchSaveAlbum: saveAlbum
|
|
||||||
};
|
|
||||||
|
|
||||||
class EditAlbumModalContentConnector extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
|
||||||
if (prevProps.isSaving && !this.props.isSaving && !this.props.saveError) {
|
|
||||||
this.props.onModalClose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onInputChange = ({ name, value }) => {
|
|
||||||
this.props.dispatchSetAlbumValue({ name, value });
|
|
||||||
}
|
|
||||||
|
|
||||||
onSavePress = () => {
|
|
||||||
this.props.dispatchSaveAlbum({
|
|
||||||
id: this.props.albumId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<EditAlbumModalContent
|
|
||||||
{...this.props}
|
|
||||||
onInputChange={this.onInputChange}
|
|
||||||
onSavePress={this.onSavePress}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
EditAlbumModalContentConnector.propTypes = {
|
|
||||||
albumId: PropTypes.number,
|
|
||||||
isSaving: PropTypes.bool.isRequired,
|
|
||||||
saveError: PropTypes.object,
|
|
||||||
dispatchSetAlbumValue: PropTypes.func.isRequired,
|
|
||||||
dispatchSaveAlbum: PropTypes.func.isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps)(EditAlbumModalContentConnector);
|
|
||||||
@@ -46,7 +46,7 @@ class EpisodeStatusConnector extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EpisodeStatusConnector.propTypes = {
|
EpisodeStatusConnector.propTypes = {
|
||||||
albumId: PropTypes.number.isRequired,
|
bookId: PropTypes.number.isRequired,
|
||||||
trackFileId: PropTypes.number.isRequired
|
trackFileId: PropTypes.number.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import AlbumInteractiveSearchModalContent from './AlbumInteractiveSearchModalCon
|
|||||||
function AlbumInteractiveSearchModal(props) {
|
function AlbumInteractiveSearchModal(props) {
|
||||||
const {
|
const {
|
||||||
isOpen,
|
isOpen,
|
||||||
albumId,
|
bookId,
|
||||||
albumTitle,
|
albumTitle,
|
||||||
onModalClose
|
onModalClose
|
||||||
} = props;
|
} = props;
|
||||||
@@ -18,7 +18,7 @@ function AlbumInteractiveSearchModal(props) {
|
|||||||
onModalClose={onModalClose}
|
onModalClose={onModalClose}
|
||||||
>
|
>
|
||||||
<AlbumInteractiveSearchModalContent
|
<AlbumInteractiveSearchModalContent
|
||||||
albumId={albumId}
|
bookId={bookId}
|
||||||
albumTitle={albumTitle}
|
albumTitle={albumTitle}
|
||||||
onModalClose={onModalClose}
|
onModalClose={onModalClose}
|
||||||
/>
|
/>
|
||||||
@@ -28,7 +28,7 @@ function AlbumInteractiveSearchModal(props) {
|
|||||||
|
|
||||||
AlbumInteractiveSearchModal.propTypes = {
|
AlbumInteractiveSearchModal.propTypes = {
|
||||||
isOpen: PropTypes.bool.isRequired,
|
isOpen: PropTypes.bool.isRequired,
|
||||||
albumId: PropTypes.number.isRequired,
|
bookId: PropTypes.number.isRequired,
|
||||||
albumTitle: PropTypes.string.isRequired,
|
albumTitle: PropTypes.string.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import InteractiveSearchConnector from 'InteractiveSearch/InteractiveSearchConne
|
|||||||
|
|
||||||
function AlbumInteractiveSearchModalContent(props) {
|
function AlbumInteractiveSearchModalContent(props) {
|
||||||
const {
|
const {
|
||||||
albumId,
|
bookId,
|
||||||
albumTitle,
|
albumTitle,
|
||||||
onModalClose
|
onModalClose
|
||||||
} = props;
|
} = props;
|
||||||
@@ -18,14 +18,14 @@ function AlbumInteractiveSearchModalContent(props) {
|
|||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
Interactive Search {albumId != null && `- ${albumTitle}`}
|
Interactive Search {bookId != null && `- ${albumTitle}`}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody scrollDirection={scrollDirections.BOTH}>
|
<ModalBody scrollDirection={scrollDirections.BOTH}>
|
||||||
<InteractiveSearchConnector
|
<InteractiveSearchConnector
|
||||||
type="album"
|
type="album"
|
||||||
searchPayload={{
|
searchPayload={{
|
||||||
albumId
|
bookId
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
@@ -40,7 +40,7 @@ function AlbumInteractiveSearchModalContent(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AlbumInteractiveSearchModalContent.propTypes = {
|
AlbumInteractiveSearchModalContent.propTypes = {
|
||||||
albumId: PropTypes.number.isRequired,
|
bookId: PropTypes.number.isRequired,
|
||||||
albumTitle: PropTypes.string.isRequired,
|
albumTitle: PropTypes.string.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const columns = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'albumCount',
|
name: 'albumCount',
|
||||||
label: 'Albums',
|
label: 'Books',
|
||||||
isSortable: false,
|
isSortable: false,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
}
|
}
|
||||||
@@ -253,7 +253,7 @@ class AlbumStudio extends Component {
|
|||||||
>
|
>
|
||||||
<AlbumStudioRowConnector
|
<AlbumStudioRowConnector
|
||||||
key={item.id}
|
key={item.id}
|
||||||
artistId={item.id}
|
authorId={item.id}
|
||||||
isSelected={selectedState[item.id]}
|
isSelected={selectedState[item.id]}
|
||||||
onSelectedChange={this.onSelectedChange}
|
onSelectedChange={this.onSelectedChange}
|
||||||
/>
|
/>
|
||||||
@@ -282,7 +282,7 @@ class AlbumStudio extends Component {
|
|||||||
|
|
||||||
onUpdateSelectedPress = (changes) => {
|
onUpdateSelectedPress = (changes) => {
|
||||||
this.props.onUpdateSelectedPress({
|
this.props.onUpdateSelectedPress({
|
||||||
artistIds: this.getSelectedIds(),
|
authorIds: this.getSelectedIds(),
|
||||||
...changes
|
...changes
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ class AlbumStudioAlbum extends Component {
|
|||||||
const {
|
const {
|
||||||
title,
|
title,
|
||||||
disambiguation,
|
disambiguation,
|
||||||
albumType,
|
|
||||||
monitored,
|
monitored,
|
||||||
statistics,
|
statistics,
|
||||||
isSaving
|
isSaving
|
||||||
@@ -53,14 +52,6 @@ class AlbumStudioAlbum extends Component {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.albumType}>
|
|
||||||
<span>
|
|
||||||
{
|
|
||||||
`${albumType}`
|
|
||||||
}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
styles.tracks,
|
styles.tracks,
|
||||||
@@ -82,7 +73,6 @@ AlbumStudioAlbum.propTypes = {
|
|||||||
id: PropTypes.number.isRequired,
|
id: PropTypes.number.isRequired,
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
disambiguation: PropTypes.string,
|
disambiguation: PropTypes.string,
|
||||||
albumType: PropTypes.string.isRequired,
|
|
||||||
monitored: PropTypes.bool.isRequired,
|
monitored: PropTypes.bool.isRequired,
|
||||||
statistics: PropTypes.object.isRequired,
|
statistics: PropTypes.object.isRequired,
|
||||||
isSaving: PropTypes.bool.isRequired,
|
isSaving: PropTypes.bool.isRequired,
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class AlbumStudioFooter extends Component {
|
|||||||
<PageContentFooter>
|
<PageContentFooter>
|
||||||
<div className={styles.inputContainer}>
|
<div className={styles.inputContainer}>
|
||||||
<div className={styles.label}>
|
<div className={styles.label}>
|
||||||
Monitor Artist
|
Monitor Author
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SelectInput
|
<SelectInput
|
||||||
@@ -103,7 +103,7 @@ class AlbumStudioFooter extends Component {
|
|||||||
|
|
||||||
<div className={styles.inputContainer}>
|
<div className={styles.inputContainer}>
|
||||||
<div className={styles.label}>
|
<div className={styles.label}>
|
||||||
Monitor Albums
|
Monitor Books
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MonitorAlbumsSelectInput
|
<MonitorAlbumsSelectInput
|
||||||
@@ -117,7 +117,7 @@ class AlbumStudioFooter extends Component {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.label}>
|
<div className={styles.label}>
|
||||||
{selectedCount} Artist(s) Selected
|
{selectedCount} Author(s) Selected
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SpinnerButton
|
<SpinnerButton
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ class AlbumStudioRow extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
artistId,
|
authorId,
|
||||||
status,
|
status,
|
||||||
foreignArtistId,
|
titleSlug,
|
||||||
artistName,
|
artistName,
|
||||||
monitored,
|
monitored,
|
||||||
albums,
|
albums,
|
||||||
@@ -33,7 +33,7 @@ class AlbumStudioRow extends Component {
|
|||||||
<>
|
<>
|
||||||
<VirtualTableSelectCell
|
<VirtualTableSelectCell
|
||||||
className={styles.selectCell}
|
className={styles.selectCell}
|
||||||
id={artistId}
|
id={authorId}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
onSelectedChange={onSelectedChange}
|
onSelectedChange={onSelectedChange}
|
||||||
isDisabled={false}
|
isDisabled={false}
|
||||||
@@ -58,7 +58,7 @@ class AlbumStudioRow extends Component {
|
|||||||
|
|
||||||
<VirtualTableRowCell className={styles.title}>
|
<VirtualTableRowCell className={styles.title}>
|
||||||
<ArtistNameLink
|
<ArtistNameLink
|
||||||
foreignArtistId={foreignArtistId}
|
titleSlug={titleSlug}
|
||||||
artistName={artistName}
|
artistName={artistName}
|
||||||
/>
|
/>
|
||||||
</VirtualTableRowCell>
|
</VirtualTableRowCell>
|
||||||
@@ -82,9 +82,9 @@ class AlbumStudioRow extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AlbumStudioRow.propTypes = {
|
AlbumStudioRow.propTypes = {
|
||||||
artistId: PropTypes.number.isRequired,
|
authorId: PropTypes.number.isRequired,
|
||||||
status: PropTypes.string.isRequired,
|
status: PropTypes.string.isRequired,
|
||||||
foreignArtistId: PropTypes.string.isRequired,
|
titleSlug: PropTypes.string.isRequired,
|
||||||
artistName: PropTypes.string.isRequired,
|
artistName: PropTypes.string.isRequired,
|
||||||
monitored: PropTypes.bool.isRequired,
|
monitored: PropTypes.bool.isRequired,
|
||||||
albums: PropTypes.arrayOf(PropTypes.object).isRequired,
|
albums: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const getAlbumMap = createSelector(
|
|||||||
(state) => state.albums.items,
|
(state) => state.albums.items,
|
||||||
(albums) => {
|
(albums) => {
|
||||||
return albums.reduce((acc, curr) => {
|
return albums.reduce((acc, curr) => {
|
||||||
(acc[curr.artistId] = acc[curr.artistId] || []).push(curr);
|
(acc[curr.authorId] = acc[curr.authorId] || []).push(curr);
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ function createMapStateToProps() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...artist,
|
...artist,
|
||||||
artistId: artist.id,
|
authorId: artist.id,
|
||||||
artistName: artist.artistName,
|
artistName: artist.artistName,
|
||||||
monitored: artist.monitored,
|
monitored: artist.monitored,
|
||||||
status: artist.status,
|
status: artist.status,
|
||||||
@@ -52,20 +52,20 @@ class AlbumStudioRowConnector extends Component {
|
|||||||
|
|
||||||
onArtistMonitoredPress = () => {
|
onArtistMonitoredPress = () => {
|
||||||
const {
|
const {
|
||||||
artistId,
|
authorId,
|
||||||
monitored
|
monitored
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
this.props.toggleArtistMonitored({
|
this.props.toggleArtistMonitored({
|
||||||
artistId,
|
authorId,
|
||||||
monitored: !monitored
|
monitored: !monitored
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onAlbumMonitoredPress = (albumId, monitored) => {
|
onAlbumMonitoredPress = (bookId, monitored) => {
|
||||||
const albumIds = [albumId];
|
const bookIds = [bookId];
|
||||||
this.props.toggleAlbumsMonitored({
|
this.props.toggleAlbumsMonitored({
|
||||||
albumIds,
|
bookIds,
|
||||||
monitored
|
monitored
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -85,7 +85,7 @@ class AlbumStudioRowConnector extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AlbumStudioRowConnector.propTypes = {
|
AlbumStudioRowConnector.propTypes = {
|
||||||
artistId: PropTypes.number.isRequired,
|
authorId: PropTypes.number.isRequired,
|
||||||
monitored: PropTypes.bool.isRequired,
|
monitored: PropTypes.bool.isRequired,
|
||||||
toggleArtistMonitored: PropTypes.func.isRequired,
|
toggleArtistMonitored: PropTypes.func.isRequired,
|
||||||
toggleAlbumsMonitored: PropTypes.func.isRequired
|
toggleAlbumsMonitored: PropTypes.func.isRequired
|
||||||
|
|||||||
@@ -91,12 +91,12 @@ function AppRoutes(props) {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="/artist/:foreignArtistId"
|
path="/author/:titleSlug"
|
||||||
component={ArtistDetailsPageConnector}
|
component={ArtistDetailsPageConnector}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="/album/:foreignAlbumId"
|
path="/book/:titleSlug"
|
||||||
component={AlbumDetailsPageConnector}
|
component={AlbumDetailsPageConnector}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -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,iVBORw0KGgoAAAANSUhEUgAAAPcAAAD3AgMAAAC84irAAAAADFBMVEUyMjI7Ozs1NTU4ODjgOsZvAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB+EJEBIzDdm9OfoAAAbkSURBVGje7Zq9b9s4FMBZFgUkBR27C3cw0MromL1jxwyVZASB67G4qWPgoSAyBdm9CwECKCp8nbIccGj/Ce/BTUb3Lh3aI997pCjnTnyyt0JcIif5+ZHvPZLvQ0KMYxzjGMc4xjGOcYxjHOP4JUfSfP7RVPvSH3MYX/eC5aecxne1v+w95WebFs/rwVO/8+h8PnT6t3ln/DFQuJ06/SyHiX9pxa7o5/lewkuLDxLvhM8tPki8g07dU8Gnj5zGlw7P79n4pDVYi8/YuHO4n03z0z6XXDom4G3TXDdN840+LobN/W1Ty2slHD8bNvevlUgutLmTj4NmT3pf6mMGcJGth+gefaZsDCjB2Wj65wN8ZmnAGnE6eFieI1FvcEISLjIUr9hm+w7PFeHiE9t0E7dyIatE48odXTPu0j/A3BMnXf7NXDxudTxbE2VxMWVu+sfwf3i1ZMLiaQLf+iWIP4VtjtTzFhc35vfveZrb4nPt4R95ulu1cxeVh8Psw7rzbgWp8dWHyr83WJpbgjypjS5XeZnqRxmJNUd3MS1d6ue/tOn0WuayNd2CoTlaeqwnIVeOgcWHdHdMS9cSN1vCy3bxZwzFm6VL7QA14WTudVj1sFvf4ReZNSCO0IvwngXFV3hkFcriuPokrPrYbYxjVAHiZ24zLYIeP7/E4xZUgHiZWt29D9ptGemHR7mPo9B10HLGbucRfs/Ww2f2CD4L2u0+wofKwwvrd0XoqCmr38CAZa1d58LesEpvgqtN4MCR1mVj2nZWOiweVB/CAXuyi59Y1auA2eekg6Xw8Tfm013A8LFV8mYXL61ZF4Hb8Zx8d9vBtbdG7s99XvOOZlF38QVtmlkAv0ffxTOjxU/o5p8FvKbSszw2ik87+Iz23Lwf134RiWf2tG3xN2T4oh8vDO4U33z+5qnefFnR77OA2wheh2WfbJBHeI/XgtNJEaHdtJNrvPn8E8eV/kW/2xn8FDc77LemOyq4J1XvSbds7SZ3cAV+86UXP283TGaFUk4ZwmNyugne8FaqxdHtFkH8GNewg2cc3PjsM7CbbNdMwQJ47aL3mP5H308ar5XOn2nUwpx+4hrx/z+qn5DBNqD4rMUpWACnPwnhkfa9SnZwvX1MnHLVi08cPle+0wBuAsykd8dO0KkS9L0dPCO37MVLxJc6nPHdTeNT/ZeLDQN/DEFpBzc33Bfckhx8K1q7IS5vuPgjbTf5AL97zcALxFUHN76QrF7heTHru54RN3bbxTeEn4Xx04f4NOfhSuPLncmnQk3z1yLlSE8fabtFHVyZyIQlXes8zrdSJR5ea7k3+asUooXg2mO4oDprT/XdHpROhouL/8A3edBw5DYxBhYdn08Q53jd0elDfApHbHjL6Hk/pvvNd1rEWdLl9iG+hpMgiMMdVEM64B8X5nq6ZBwX5rCSeK/4uInJROiwetLi0jtpG0yJBPOkTVQXryEPKqMQbq6JeyUTvUOkilq/EVGmo5NIpP3XRIzhXIafrjzF30JUIqecKxIjOpF6il9jbHTLxjs3rN5voPH+GxbDA1m7GrM9a4zdTigdCUUXD2MSSEAXQRxDo2QHl2iwV+h7gchqLrLrhmKxH/Z6nqLUQD5AYSHWAEwk+Z1Ck1vEAmEhBaVtufDtj8Zmv6U+PQNBqbDf/szVR5XNvQteSAzRyeQhzgnIKR2Invq43gQb4+oRaJCTTcRd6RkzGXlJQe3vDq8gsDB2S0QaSoViwKNW9Sh9zUzEMA2MWtU7nJUGYhIa4bnjcLthgkkopMAGj3dxXgoMCbg+laTFL8luSn9pFkrAMf031cmVJz0jXzsKFm6OSfVqYnEILPKZDjeicPFhQoaHbMhKX+NmZ5Q+ntr8n5obhGPVKlx48cs+FteKP3MlswWv6CSPHK4Dmntm0ckreW0snmxKbsnLFdyo4mrwjLYJo+Dmyn0k3uDTEpMRTrnPKza+IHy9wGSEU2yMvSrvHeJ/Qt2UV+p0hVacvsah0psKXqEVy7y2tPu3xhM1oMxLReY00tAlJG9JFZktzCwyU4lbuqQ7U22VN1zi9gvsIP05PjAL7H55H/C6rREzyvu41bbS4VXb1OV0FLG1YVsa1J1gtzaosVJbHO3Gb6z4bR2H89s61FRqCIcgL+E3lfyWlsaN3eR6QDP0pSdeKqOEZjOgoda285SUl5W+Jga181wz0WQFF2poM7FtZTZKXlXZ0Fam10htroY3Ug9s43pN5OJ2jyZy28Iu1nu0sNsGenGzRwO9bd8Xd/u0793LA8Vmn5cHnPhiH+Gt+HIv4Ye+tnHoSyMHvrJy6Aszh76uc+DLQuLQV5XGMY5xjGMc4xjHOMYxjnH80uNfW99BeoyzJCoAAAAASUVORK5CYII=';
|
|
||||||
|
|
||||||
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 React from 'react';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
|
|
||||||
function ArtistNameLink({ foreignArtistId, artistName }) {
|
function ArtistNameLink({ titleSlug, artistName }) {
|
||||||
const link = `/artist/${foreignArtistId}`;
|
const link = `/author/${titleSlug}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to={link}>
|
<Link to={link}>
|
||||||
@@ -13,7 +13,7 @@ function ArtistNameLink({ foreignArtistId, artistName }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ArtistNameLink.propTypes = {
|
ArtistNameLink.propTypes = {
|
||||||
foreignArtistId: PropTypes.string.isRequired,
|
titleSlug: PropTypes.string.isRequired,
|
||||||
artistName: PropTypes.string.isRequired
|
artistName: PropTypes.string.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ArtistImage from './ArtistImage';
|
import ArtistImage from './ArtistImage';
|
||||||
|
|
||||||
const posterPlaceholder = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPcAAAD3AgMAAAC84irAAAAADFBMVEUyMjI7Ozs1NTU4ODjgOsZvAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB+EJEBIzDdm9OfoAAAbkSURBVGje7Zq9b9s4FMBZFgUkBR27C3cw0MromL1jxwyVZASB67G4qWPgoSAyBdm9CwECKCp8nbIccGj/Ce/BTUb3Lh3aI997pCjnTnyyt0JcIif5+ZHvPZLvQ0KMYxzjGMc4xjGOcYxjHOP4JUfSfP7RVPvSH3MYX/eC5aecxne1v+w95WebFs/rwVO/8+h8PnT6t3ln/DFQuJ06/SyHiX9pxa7o5/lewkuLDxLvhM8tPki8g07dU8Gnj5zGlw7P79n4pDVYi8/YuHO4n03z0z6XXDom4G3TXDdN840+LobN/W1Ty2slHD8bNvevlUgutLmTj4NmT3pf6mMGcJGth+gefaZsDCjB2Wj65wN8ZmnAGnE6eFieI1FvcEISLjIUr9hm+w7PFeHiE9t0E7dyIatE48odXTPu0j/A3BMnXf7NXDxudTxbE2VxMWVu+sfwf3i1ZMLiaQLf+iWIP4VtjtTzFhc35vfveZrb4nPt4R95ulu1cxeVh8Psw7rzbgWp8dWHyr83WJpbgjypjS5XeZnqRxmJNUd3MS1d6ue/tOn0WuayNd2CoTlaeqwnIVeOgcWHdHdMS9cSN1vCy3bxZwzFm6VL7QA14WTudVj1sFvf4ReZNSCO0IvwngXFV3hkFcriuPokrPrYbYxjVAHiZ24zLYIeP7/E4xZUgHiZWt29D9ptGemHR7mPo9B10HLGbucRfs/Ww2f2CD4L2u0+wofKwwvrd0XoqCmr38CAZa1d58LesEpvgqtN4MCR1mVj2nZWOiweVB/CAXuyi59Y1auA2eekg6Xw8Tfm013A8LFV8mYXL61ZF4Hb8Zx8d9vBtbdG7s99XvOOZlF38QVtmlkAv0ffxTOjxU/o5p8FvKbSszw2ik87+Iz23Lwf134RiWf2tG3xN2T4oh8vDO4U33z+5qnefFnR77OA2wheh2WfbJBHeI/XgtNJEaHdtJNrvPn8E8eV/kW/2xn8FDc77LemOyq4J1XvSbds7SZ3cAV+86UXP283TGaFUk4ZwmNyugne8FaqxdHtFkH8GNewg2cc3PjsM7CbbNdMwQJ47aL3mP5H308ar5XOn2nUwpx+4hrx/z+qn5DBNqD4rMUpWACnPwnhkfa9SnZwvX1MnHLVi08cPle+0wBuAsykd8dO0KkS9L0dPCO37MVLxJc6nPHdTeNT/ZeLDQN/DEFpBzc33Bfckhx8K1q7IS5vuPgjbTf5AL97zcALxFUHN76QrF7heTHru54RN3bbxTeEn4Xx04f4NOfhSuPLncmnQk3z1yLlSE8fabtFHVyZyIQlXes8zrdSJR5ea7k3+asUooXg2mO4oDprT/XdHpROhouL/8A3edBw5DYxBhYdn08Q53jd0elDfApHbHjL6Hk/pvvNd1rEWdLl9iG+hpMgiMMdVEM64B8X5nq6ZBwX5rCSeK/4uInJROiwetLi0jtpG0yJBPOkTVQXryEPKqMQbq6JeyUTvUOkilq/EVGmo5NIpP3XRIzhXIafrjzF30JUIqecKxIjOpF6il9jbHTLxjs3rN5voPH+GxbDA1m7GrM9a4zdTigdCUUXD2MSSEAXQRxDo2QHl2iwV+h7gchqLrLrhmKxH/Z6nqLUQD5AYSHWAEwk+Z1Ck1vEAmEhBaVtufDtj8Zmv6U+PQNBqbDf/szVR5XNvQteSAzRyeQhzgnIKR2Invq43gQb4+oRaJCTTcRd6RkzGXlJQe3vDq8gsDB2S0QaSoViwKNW9Sh9zUzEMA2MWtU7nJUGYhIa4bnjcLthgkkopMAGj3dxXgoMCbg+laTFL8luSn9pFkrAMf031cmVJz0jXzsKFm6OSfVqYnEILPKZDjeicPFhQoaHbMhKX+NmZ5Q+ntr8n5obhGPVKlx48cs+FteKP3MlswWv6CSPHK4Dmntm0ckreW0snmxKbsnLFdyo4mrwjLYJo+Dmyn0k3uDTEpMRTrnPKza+IHy9wGSEU2yMvSrvHeJ/Qt2UV+p0hVacvsah0psKXqEVy7y2tPu3xhM1oMxLReY00tAlJG9JFZktzCwyU4lbuqQ7U22VN1zi9gvsIP05PjAL7H55H/C6rREzyvu41bbS4VXb1OV0FLG1YVsa1J1gtzaosVJbHO3Gb6z4bR2H89s61FRqCIcgL+E3lfyWlsaN3eR6QDP0pSdeKqOEZjOgoda285SUl5W+Jga181wz0WQFF2poM7FtZTZKXlXZ0Fam10htroY3Ug9s43pN5OJ2jyZy28Iu1nu0sNsGenGzRwO9bd8Xd/u0793LA8Vmn5cHnPhiH+Gt+HIv4Ye+tnHoSyMHvrJy6Aszh76uc+DLQuLQV5XGMY5xjGMc4xjHOMYxjnH80uNfW99BeoyzJCoAAAAASUVORK5CYII=';
|
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) {
|
function ArtistPoster(props) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class DeleteArtistModalContentConnector extends Component {
|
|||||||
|
|
||||||
onDeletePress = (deleteFiles, addImportListExclusion) => {
|
onDeletePress = (deleteFiles, addImportListExclusion) => {
|
||||||
this.props.deleteArtist({
|
this.props.deleteArtist({
|
||||||
id: this.props.artistId,
|
id: this.props.authorId,
|
||||||
deleteFiles,
|
deleteFiles,
|
||||||
addImportListExclusion
|
addImportListExclusion
|
||||||
});
|
});
|
||||||
@@ -48,7 +48,7 @@ class DeleteArtistModalContentConnector extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DeleteArtistModalContentConnector.propTypes = {
|
DeleteArtistModalContentConnector.propTypes = {
|
||||||
artistId: PropTypes.number.isRequired,
|
authorId: PropTypes.number.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired,
|
onModalClose: PropTypes.func.isRequired,
|
||||||
deleteArtist: PropTypes.func.isRequired
|
deleteArtist: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { kinds, sizes } from 'Helpers/Props';
|
|||||||
import TableRow from 'Components/Table/TableRow';
|
import TableRow from 'Components/Table/TableRow';
|
||||||
import Label from 'Components/Label';
|
import Label from 'Components/Label';
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
|
|
||||||
import AlbumSearchCellConnector from 'Album/AlbumSearchCellConnector';
|
import AlbumSearchCellConnector from 'Album/AlbumSearchCellConnector';
|
||||||
import AlbumTitleLink from 'Album/AlbumTitleLink';
|
import AlbumTitleLink from 'Album/AlbumTitleLink';
|
||||||
import StarRating from 'Components/StarRating';
|
import StarRating from 'Components/StarRating';
|
||||||
@@ -67,19 +66,17 @@ class AlbumRow extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
artistId,
|
authorId,
|
||||||
monitored,
|
monitored,
|
||||||
statistics,
|
statistics,
|
||||||
duration,
|
|
||||||
releaseDate,
|
releaseDate,
|
||||||
mediumCount,
|
|
||||||
secondaryTypes,
|
|
||||||
title,
|
title,
|
||||||
|
position,
|
||||||
ratings,
|
ratings,
|
||||||
disambiguation,
|
disambiguation,
|
||||||
isSaving,
|
isSaving,
|
||||||
artistMonitored,
|
artistMonitored,
|
||||||
foreignAlbumId,
|
titleSlug,
|
||||||
columns
|
columns
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@@ -125,7 +122,7 @@ class AlbumRow extends Component {
|
|||||||
className={styles.title}
|
className={styles.title}
|
||||||
>
|
>
|
||||||
<AlbumTitleLink
|
<AlbumTitleLink
|
||||||
foreignAlbumId={foreignAlbumId}
|
titleSlug={titleSlug}
|
||||||
title={title}
|
title={title}
|
||||||
disambiguation={disambiguation}
|
disambiguation={disambiguation}
|
||||||
/>
|
/>
|
||||||
@@ -133,42 +130,13 @@ class AlbumRow extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name === 'mediumCount') {
|
if (name === 'position') {
|
||||||
return (
|
return (
|
||||||
<TableRowCell key={name}>
|
<TableRowCell
|
||||||
{
|
key={name}
|
||||||
mediumCount
|
className={styles.title}
|
||||||
}
|
>
|
||||||
</TableRowCell>
|
{position || ''}
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
</TableRowCell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -218,8 +186,8 @@ class AlbumRow extends Component {
|
|||||||
return (
|
return (
|
||||||
<AlbumSearchCellConnector
|
<AlbumSearchCellConnector
|
||||||
key={name}
|
key={name}
|
||||||
albumId={id}
|
bookId={id}
|
||||||
artistId={artistId}
|
authorId={authorId}
|
||||||
albumTitle={title}
|
albumTitle={title}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -234,21 +202,17 @@ class AlbumRow extends Component {
|
|||||||
|
|
||||||
AlbumRow.propTypes = {
|
AlbumRow.propTypes = {
|
||||||
id: PropTypes.number.isRequired,
|
id: PropTypes.number.isRequired,
|
||||||
artistId: PropTypes.number.isRequired,
|
authorId: PropTypes.number.isRequired,
|
||||||
monitored: PropTypes.bool.isRequired,
|
monitored: PropTypes.bool.isRequired,
|
||||||
releaseDate: PropTypes.string.isRequired,
|
releaseDate: PropTypes.string,
|
||||||
mediumCount: PropTypes.number.isRequired,
|
|
||||||
duration: PropTypes.number.isRequired,
|
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
|
position: PropTypes.string,
|
||||||
ratings: PropTypes.object.isRequired,
|
ratings: PropTypes.object.isRequired,
|
||||||
disambiguation: PropTypes.string,
|
disambiguation: PropTypes.string,
|
||||||
secondaryTypes: PropTypes.arrayOf(PropTypes.string).isRequired,
|
titleSlug: PropTypes.string.isRequired,
|
||||||
foreignAlbumId: PropTypes.string.isRequired,
|
|
||||||
isSaving: PropTypes.bool,
|
isSaving: PropTypes.bool,
|
||||||
unverifiedSceneNumbering: PropTypes.bool,
|
|
||||||
artistMonitored: PropTypes.bool.isRequired,
|
artistMonitored: PropTypes.bool.isRequired,
|
||||||
statistics: PropTypes.object.isRequired,
|
statistics: PropTypes.object.isRequired,
|
||||||
mediaInfo: PropTypes.object,
|
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
onMonitorAlbumPress: PropTypes.func.isRequired
|
onMonitorAlbumPress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ function createMapStateToProps() {
|
|||||||
createTrackFileSelector(),
|
createTrackFileSelector(),
|
||||||
(artist = {}, trackFile) => {
|
(artist = {}, trackFile) => {
|
||||||
return {
|
return {
|
||||||
foreignArtistId: artist.foreignArtistId,
|
|
||||||
artistMonitored: artist.monitored,
|
artistMonitored: artist.monitored,
|
||||||
trackFilePath: trackFile ? trackFile.path : null
|
trackFilePath: trackFile ? trackFile.path : null
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -41,7 +41,6 @@
|
|||||||
.poster {
|
.poster {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-right: 35px;
|
margin-right: 35px;
|
||||||
width: 250px;
|
|
||||||
height: 250px;
|
height: 250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,6 +95,10 @@
|
|||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filterIcon {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
.artistNavigationButtons {
|
.artistNavigationButtons {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
@@ -150,6 +153,31 @@
|
|||||||
padding: 20px;
|
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) {
|
@media only screen and (max-width: $breakpointSmall) {
|
||||||
.contentContainer {
|
.contentContainer {
|
||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
|
||||||
import TextTruncate from 'react-text-truncate';
|
import TextTruncate from 'react-text-truncate';
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
import selectAll from 'Utilities/Table/selectAll';
|
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 PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||||
import Popover from 'Components/Tooltip/Popover';
|
import Popover from 'Components/Tooltip/Popover';
|
||||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||||
import TrackFileEditorModal from 'TrackFile/Editor/TrackFileEditorModal';
|
import TrackFileEditorTable from 'TrackFile/Editor/TrackFileEditorTable';
|
||||||
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
|
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
|
||||||
import RetagPreviewModalConnector from 'Retag/RetagPreviewModalConnector';
|
import RetagPreviewModalConnector from 'Retag/RetagPreviewModalConnector';
|
||||||
import QualityProfileNameConnector from 'Settings/Profiles/Quality/QualityProfileNameConnector';
|
import QualityProfileNameConnector from 'Settings/Profiles/Quality/QualityProfileNameConnector';
|
||||||
import ArtistPoster from 'Artist/ArtistPoster';
|
import ArtistPoster from 'Artist/ArtistPoster';
|
||||||
import EditArtistModalConnector from 'Artist/Edit/EditArtistModalConnector';
|
import EditArtistModalConnector from 'Artist/Edit/EditArtistModalConnector';
|
||||||
import DeleteArtistModal from 'Artist/Delete/DeleteArtistModal';
|
import DeleteArtistModal from 'Artist/Delete/DeleteArtistModal';
|
||||||
import ArtistHistoryModal from 'Artist/History/ArtistHistoryModal';
|
import ArtistHistoryTable from 'Artist/History/ArtistHistoryTable';
|
||||||
import ArtistAlternateTitles from './ArtistAlternateTitles';
|
import ArtistAlternateTitles from './ArtistAlternateTitles';
|
||||||
import ArtistDetailsSeasonConnector from './ArtistDetailsSeasonConnector';
|
import ArtistDetailsSeasonConnector from './ArtistDetailsSeasonConnector';
|
||||||
|
import AuthorDetailsSeriesConnector from './AuthorDetailsSeriesConnector';
|
||||||
import ArtistTagsConnector from './ArtistTagsConnector';
|
import ArtistTagsConnector from './ArtistTagsConnector';
|
||||||
import ArtistDetailsLinks from './ArtistDetailsLinks';
|
import ArtistDetailsLinks from './ArtistDetailsLinks';
|
||||||
import styles from './ArtistDetails.css';
|
import styles from './ArtistDetails.css';
|
||||||
|
import InteractiveSearchTable from 'InteractiveSearch/InteractiveSearchTable';
|
||||||
|
import InteractiveSearchFilterMenuConnector from 'InteractiveSearch/InteractiveSearchFilterMenuConnector';
|
||||||
import InteractiveImportModal from '../../InteractiveImport/InteractiveImportModal';
|
import InteractiveImportModal from '../../InteractiveImport/InteractiveImportModal';
|
||||||
import ArtistInteractiveSearchModalConnector from 'Artist/Search/ArtistInteractiveSearchModalConnector';
|
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
|
|
||||||
const defaultFontSize = parseInt(fonts.defaultFontSize);
|
const defaultFontSize = parseInt(fonts.defaultFontSize);
|
||||||
@@ -68,15 +71,13 @@ class ArtistDetails extends Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
isOrganizeModalOpen: false,
|
isOrganizeModalOpen: false,
|
||||||
isRetagModalOpen: false,
|
isRetagModalOpen: false,
|
||||||
isManageTracksOpen: false,
|
|
||||||
isEditArtistModalOpen: false,
|
isEditArtistModalOpen: false,
|
||||||
isDeleteArtistModalOpen: false,
|
isDeleteArtistModalOpen: false,
|
||||||
isArtistHistoryModalOpen: false,
|
|
||||||
isInteractiveImportModalOpen: false,
|
isInteractiveImportModalOpen: false,
|
||||||
isInteractiveSearchModalOpen: false,
|
|
||||||
allExpanded: false,
|
allExpanded: false,
|
||||||
allCollapsed: false,
|
allCollapsed: false,
|
||||||
expandedState: {}
|
expandedState: {},
|
||||||
|
selectedTabIndex: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,14 +100,6 @@ class ArtistDetails extends Component {
|
|||||||
this.setState({ isRetagModalOpen: false });
|
this.setState({ isRetagModalOpen: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
onManageTracksPress = () => {
|
|
||||||
this.setState({ isManageTracksOpen: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
onManageTracksModalClose = () => {
|
|
||||||
this.setState({ isManageTracksOpen: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
onInteractiveImportPress = () => {
|
onInteractiveImportPress = () => {
|
||||||
this.setState({ isInteractiveImportModalOpen: true });
|
this.setState({ isInteractiveImportModalOpen: true });
|
||||||
}
|
}
|
||||||
@@ -115,14 +108,6 @@ class ArtistDetails extends Component {
|
|||||||
this.setState({ isInteractiveImportModalOpen: false });
|
this.setState({ isInteractiveImportModalOpen: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
onInteractiveSearchPress = () => {
|
|
||||||
this.setState({ isInteractiveSearchModalOpen: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
onInteractiveSearchModalClose = () => {
|
|
||||||
this.setState({ isInteractiveSearchModalOpen: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
onEditArtistPress = () => {
|
onEditArtistPress = () => {
|
||||||
this.setState({ isEditArtistModalOpen: true });
|
this.setState({ isEditArtistModalOpen: true });
|
||||||
}
|
}
|
||||||
@@ -142,14 +127,6 @@ class ArtistDetails extends Component {
|
|||||||
this.setState({ isDeleteArtistModalOpen: false });
|
this.setState({ isDeleteArtistModalOpen: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
onArtistHistoryPress = () => {
|
|
||||||
this.setState({ isArtistHistoryModalOpen: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
onArtistHistoryModalClose = () => {
|
|
||||||
this.setState({ isArtistHistoryModalOpen: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
onExpandAllPress = () => {
|
onExpandAllPress = () => {
|
||||||
const {
|
const {
|
||||||
allExpanded,
|
allExpanded,
|
||||||
@@ -159,7 +136,7 @@ class ArtistDetails extends Component {
|
|||||||
this.setState(getExpandedState(selectAll(expandedState, !allExpanded)));
|
this.setState(getExpandedState(selectAll(expandedState, !allExpanded)));
|
||||||
}
|
}
|
||||||
|
|
||||||
onExpandPress = (albumId, isExpanded) => {
|
onExpandPress = (bookId, isExpanded) => {
|
||||||
this.setState((state) => {
|
this.setState((state) => {
|
||||||
const convertedState = {
|
const convertedState = {
|
||||||
allSelected: state.allExpanded,
|
allSelected: state.allExpanded,
|
||||||
@@ -167,7 +144,7 @@ class ArtistDetails extends Component {
|
|||||||
selectedState: state.expandedState
|
selectedState: state.expandedState
|
||||||
};
|
};
|
||||||
|
|
||||||
const newState = toggleSelected(convertedState, [], albumId, isExpanded, false);
|
const newState = toggleSelected(convertedState, [], bookId, isExpanded, false);
|
||||||
|
|
||||||
return getExpandedState(newState);
|
return getExpandedState(newState);
|
||||||
});
|
});
|
||||||
@@ -179,14 +156,12 @@ class ArtistDetails extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
foreignArtistId,
|
|
||||||
artistName,
|
artistName,
|
||||||
ratings,
|
ratings,
|
||||||
path,
|
path,
|
||||||
statistics,
|
statistics,
|
||||||
qualityProfileId,
|
qualityProfileId,
|
||||||
monitored,
|
monitored,
|
||||||
albumTypes,
|
|
||||||
status,
|
status,
|
||||||
overview,
|
overview,
|
||||||
links,
|
links,
|
||||||
@@ -203,6 +178,8 @@ class ArtistDetails extends Component {
|
|||||||
trackFilesError,
|
trackFilesError,
|
||||||
hasAlbums,
|
hasAlbums,
|
||||||
hasMonitoredAlbums,
|
hasMonitoredAlbums,
|
||||||
|
hasSeries,
|
||||||
|
series,
|
||||||
hasTrackFiles,
|
hasTrackFiles,
|
||||||
previousArtist,
|
previousArtist,
|
||||||
nextArtist,
|
nextArtist,
|
||||||
@@ -219,15 +196,13 @@ class ArtistDetails extends Component {
|
|||||||
const {
|
const {
|
||||||
isOrganizeModalOpen,
|
isOrganizeModalOpen,
|
||||||
isRetagModalOpen,
|
isRetagModalOpen,
|
||||||
isManageTracksOpen,
|
|
||||||
isEditArtistModalOpen,
|
isEditArtistModalOpen,
|
||||||
isDeleteArtistModalOpen,
|
isDeleteArtistModalOpen,
|
||||||
isArtistHistoryModalOpen,
|
|
||||||
isInteractiveImportModalOpen,
|
isInteractiveImportModalOpen,
|
||||||
isInteractiveSearchModalOpen,
|
|
||||||
allExpanded,
|
allExpanded,
|
||||||
allCollapsed,
|
allCollapsed,
|
||||||
expandedState
|
expandedState,
|
||||||
|
selectedTabIndex
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const continuing = status === 'continuing';
|
const continuing = status === 'continuing';
|
||||||
@@ -271,15 +246,6 @@ class ArtistDetails extends Component {
|
|||||||
onPress={onSearchPress}
|
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 />
|
<PageToolbarSeparator />
|
||||||
|
|
||||||
<PageToolbarButton
|
<PageToolbarButton
|
||||||
@@ -289,26 +255,12 @@ class ArtistDetails extends Component {
|
|||||||
onPress={this.onOrganizePress}
|
onPress={this.onOrganizePress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PageToolbarButton
|
{/* <PageToolbarButton */}
|
||||||
label="Preview Retag"
|
{/* label="Preview Retag" */}
|
||||||
iconName={icons.RETAG}
|
{/* iconName={icons.RETAG} */}
|
||||||
isDisabled={!hasTrackFiles}
|
{/* isDisabled={!hasTrackFiles} */}
|
||||||
onPress={this.onRetagPress}
|
{/* 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
|
<PageToolbarButton
|
||||||
label="Manual Import"
|
label="Manual Import"
|
||||||
@@ -400,7 +352,7 @@ class ArtistDetails extends Component {
|
|||||||
name={icons.ARROW_LEFT}
|
name={icons.ARROW_LEFT}
|
||||||
size={30}
|
size={30}
|
||||||
title={`Go to ${previousArtist.artistName}`}
|
title={`Go to ${previousArtist.artistName}`}
|
||||||
to={`/artist/${previousArtist.foreignArtistId}`}
|
to={`/author/${previousArtist.titleSlug}`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -416,7 +368,7 @@ class ArtistDetails extends Component {
|
|||||||
name={icons.ARROW_RIGHT}
|
name={icons.ARROW_RIGHT}
|
||||||
size={30}
|
size={30}
|
||||||
title={`Go to ${nextArtist.artistName}`}
|
title={`Go to ${nextArtist.artistName}`}
|
||||||
to={`/artist/${nextArtist.foreignArtistId}`}
|
to={`/author/${nextArtist.titleSlug}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -528,7 +480,6 @@ class ArtistDetails extends Component {
|
|||||||
}
|
}
|
||||||
tooltip={
|
tooltip={
|
||||||
<ArtistDetailsLinks
|
<ArtistDetailsLinks
|
||||||
foreignArtistId={foreignArtistId}
|
|
||||||
links={links}
|
links={links}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@@ -554,7 +505,7 @@ class ArtistDetails extends Component {
|
|||||||
</span>
|
</span>
|
||||||
</Label>
|
</Label>
|
||||||
}
|
}
|
||||||
tooltip={<ArtistTagsConnector artistId={id} />}
|
tooltip={<ArtistTagsConnector authorId={id} />}
|
||||||
kind={kinds.INVERSE}
|
kind={kinds.INVERSE}
|
||||||
position={tooltipPositions.BOTTOM}
|
position={tooltipPositions.BOTTOM}
|
||||||
/>
|
/>
|
||||||
@@ -564,7 +515,7 @@ class ArtistDetails extends Component {
|
|||||||
<div className={styles.overview}>
|
<div className={styles.overview}>
|
||||||
<TextTruncate
|
<TextTruncate
|
||||||
line={Math.floor(125 / (defaultFontSize * lineHeight))}
|
line={Math.floor(125 / (defaultFontSize * lineHeight))}
|
||||||
text={overview}
|
text={overview.replace(/<[^>]*>?/gm, '')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -588,30 +539,110 @@ class ArtistDetails extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
isPopulated && !!albumTypes.length &&
|
isPopulated &&
|
||||||
<div>
|
<Tabs selectedIndex={this.state.tabIndex} onSelect={(tabIndex) => this.setState({ selectedTabIndex: tabIndex })}>
|
||||||
{
|
<TabList
|
||||||
albumTypes.slice(0).map((albumType) => {
|
className={styles.tabList}
|
||||||
return (
|
>
|
||||||
<ArtistDetailsSeasonConnector
|
<Tab
|
||||||
key={albumType}
|
className={styles.tab}
|
||||||
artistId={id}
|
selectedClassName={styles.selectedTab}
|
||||||
name={albumType}
|
>
|
||||||
label={albumType}
|
Books
|
||||||
{...albumType}
|
</Tab>
|
||||||
isExpanded={expandedState[albumType]}
|
|
||||||
onExpandPress={this.onExpandPress}
|
<Tab
|
||||||
/>
|
className={styles.tab}
|
||||||
);
|
selectedClassName={styles.selectedTab}
|
||||||
})
|
>
|
||||||
}
|
Series
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div className={styles.metadataMessage}>
|
<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>
|
<Link to='/settings/profiles'> Metadata Profile </Link>
|
||||||
or manually
|
or manually
|
||||||
<Link to={`/add/search?term=${encodeURIComponent(artistName)}`}> Search </Link>
|
<Link to={`/add/search?term=${encodeURIComponent(artistName)}`}> Search </Link>
|
||||||
@@ -620,38 +651,26 @@ class ArtistDetails extends Component {
|
|||||||
|
|
||||||
<OrganizePreviewModalConnector
|
<OrganizePreviewModalConnector
|
||||||
isOpen={isOrganizeModalOpen}
|
isOpen={isOrganizeModalOpen}
|
||||||
artistId={id}
|
authorId={id}
|
||||||
onModalClose={this.onOrganizeModalClose}
|
onModalClose={this.onOrganizeModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<RetagPreviewModalConnector
|
<RetagPreviewModalConnector
|
||||||
isOpen={isRetagModalOpen}
|
isOpen={isRetagModalOpen}
|
||||||
artistId={id}
|
authorId={id}
|
||||||
onModalClose={this.onRetagModalClose}
|
onModalClose={this.onRetagModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TrackFileEditorModal
|
|
||||||
isOpen={isManageTracksOpen}
|
|
||||||
artistId={id}
|
|
||||||
onModalClose={this.onManageTracksModalClose}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ArtistHistoryModal
|
|
||||||
isOpen={isArtistHistoryModalOpen}
|
|
||||||
artistId={id}
|
|
||||||
onModalClose={this.onArtistHistoryModalClose}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<EditArtistModalConnector
|
<EditArtistModalConnector
|
||||||
isOpen={isEditArtistModalOpen}
|
isOpen={isEditArtistModalOpen}
|
||||||
artistId={id}
|
authorId={id}
|
||||||
onModalClose={this.onEditArtistModalClose}
|
onModalClose={this.onEditArtistModalClose}
|
||||||
onDeleteArtistPress={this.onDeleteArtistPress}
|
onDeleteArtistPress={this.onDeleteArtistPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeleteArtistModal
|
<DeleteArtistModal
|
||||||
isOpen={isDeleteArtistModalOpen}
|
isOpen={isDeleteArtistModalOpen}
|
||||||
artistId={id}
|
authorId={id}
|
||||||
onModalClose={this.onDeleteArtistModalClose}
|
onModalClose={this.onDeleteArtistModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -663,12 +682,6 @@ class ArtistDetails extends Component {
|
|||||||
showImportMode={false}
|
showImportMode={false}
|
||||||
onModalClose={this.onInteractiveImportModalClose}
|
onModalClose={this.onInteractiveImportModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ArtistInteractiveSearchModalConnector
|
|
||||||
isOpen={isInteractiveSearchModalOpen}
|
|
||||||
artistId={id}
|
|
||||||
onModalClose={this.onInteractiveSearchModalClose}
|
|
||||||
/>
|
|
||||||
</PageContentBodyConnector>
|
</PageContentBodyConnector>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
);
|
);
|
||||||
@@ -677,7 +690,6 @@ class ArtistDetails extends Component {
|
|||||||
|
|
||||||
ArtistDetails.propTypes = {
|
ArtistDetails.propTypes = {
|
||||||
id: PropTypes.number.isRequired,
|
id: PropTypes.number.isRequired,
|
||||||
foreignArtistId: PropTypes.string.isRequired,
|
|
||||||
artistName: PropTypes.string.isRequired,
|
artistName: PropTypes.string.isRequired,
|
||||||
ratings: PropTypes.object.isRequired,
|
ratings: PropTypes.object.isRequired,
|
||||||
path: PropTypes.string.isRequired,
|
path: PropTypes.string.isRequired,
|
||||||
@@ -685,7 +697,6 @@ ArtistDetails.propTypes = {
|
|||||||
qualityProfileId: PropTypes.number.isRequired,
|
qualityProfileId: PropTypes.number.isRequired,
|
||||||
monitored: PropTypes.bool.isRequired,
|
monitored: PropTypes.bool.isRequired,
|
||||||
artistType: PropTypes.string,
|
artistType: PropTypes.string,
|
||||||
albumTypes: PropTypes.arrayOf(PropTypes.string),
|
|
||||||
status: PropTypes.string.isRequired,
|
status: PropTypes.string.isRequired,
|
||||||
overview: PropTypes.string.isRequired,
|
overview: PropTypes.string.isRequired,
|
||||||
links: PropTypes.arrayOf(PropTypes.object).isRequired,
|
links: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
@@ -701,6 +712,8 @@ ArtistDetails.propTypes = {
|
|||||||
trackFilesError: PropTypes.object,
|
trackFilesError: PropTypes.object,
|
||||||
hasAlbums: PropTypes.bool.isRequired,
|
hasAlbums: PropTypes.bool.isRequired,
|
||||||
hasMonitoredAlbums: PropTypes.bool.isRequired,
|
hasMonitoredAlbums: PropTypes.bool.isRequired,
|
||||||
|
hasSeries: PropTypes.bool.isRequired,
|
||||||
|
series: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
hasTrackFiles: PropTypes.bool.isRequired,
|
hasTrackFiles: PropTypes.bool.isRequired,
|
||||||
previousArtist: PropTypes.object.isRequired,
|
previousArtist: PropTypes.object.isRequired,
|
||||||
nextArtist: PropTypes.object.isRequired,
|
nextArtist: PropTypes.object.isRequired,
|
||||||
|
|||||||
@@ -6,12 +6,15 @@ import { connect } from 'react-redux';
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { findCommand, isCommandExecuting } from 'Utilities/Command';
|
import { findCommand, isCommandExecuting } from 'Utilities/Command';
|
||||||
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
||||||
|
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
||||||
import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
|
import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
|
||||||
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
|
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
|
||||||
import { fetchAlbums, clearAlbums } from 'Store/Actions/albumActions';
|
import { fetchAlbums, clearAlbums } from 'Store/Actions/albumActions';
|
||||||
|
import { fetchSeries, clearSeries } from 'Store/Actions/seriesActions';
|
||||||
import { fetchTrackFiles, clearTrackFiles } from 'Store/Actions/trackFileActions';
|
import { fetchTrackFiles, clearTrackFiles } from 'Store/Actions/trackFileActions';
|
||||||
import { toggleArtistMonitored } from 'Store/Actions/artistActions';
|
import { toggleArtistMonitored } from 'Store/Actions/artistActions';
|
||||||
import { fetchQueueDetails, clearQueueDetails } from 'Store/Actions/queueActions';
|
import { fetchQueueDetails, clearQueueDetails } from 'Store/Actions/queueActions';
|
||||||
|
import { clearReleases, cancelFetchReleases } from 'Store/Actions/releaseActions';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
import * as commandNames from 'Commands/commandNames';
|
import * as commandNames from 'Commands/commandNames';
|
||||||
import ArtistDetails from './ArtistDetails';
|
import ArtistDetails from './ArtistDetails';
|
||||||
@@ -28,15 +31,36 @@ const selectAlbums = createSelector(
|
|||||||
|
|
||||||
const hasAlbums = !!items.length;
|
const hasAlbums = !!items.length;
|
||||||
const hasMonitoredAlbums = items.some((e) => e.monitored);
|
const hasMonitoredAlbums = items.some((e) => e.monitored);
|
||||||
const albumTypes = _.uniq(_.map(items, 'albumType'));
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isAlbumsFetching: isFetching,
|
isAlbumsFetching: isFetching,
|
||||||
isAlbumsPopulated: isPopulated,
|
isAlbumsPopulated: isPopulated,
|
||||||
albumsError: error,
|
albumsError: error,
|
||||||
hasAlbums,
|
hasAlbums,
|
||||||
hasMonitoredAlbums,
|
hasMonitoredAlbums
|
||||||
albumTypes
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
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() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state, { foreignArtistId }) => foreignArtistId,
|
(state, { titleSlug }) => titleSlug,
|
||||||
selectAlbums,
|
selectAlbums,
|
||||||
|
selectSeries,
|
||||||
selectTrackFiles,
|
selectTrackFiles,
|
||||||
createAllArtistSelector(),
|
createAllArtistSelector(),
|
||||||
createCommandsSelector(),
|
createCommandsSelector(),
|
||||||
(foreignArtistId, albums, trackFiles, allArtists, commands) => {
|
(titleSlug, albums, series, trackFiles, allArtists, commands) => {
|
||||||
const sortedArtist = _.orderBy(allArtists, 'sortName');
|
const sortedArtist = _.orderBy(allArtists, 'sortName');
|
||||||
const artistIndex = _.findIndex(sortedArtist, { foreignArtistId });
|
const artistIndex = _.findIndex(sortedArtist, { titleSlug });
|
||||||
const artist = sortedArtist[artistIndex];
|
const artist = sortedArtist[artistIndex];
|
||||||
|
|
||||||
if (!artist) {
|
if (!artist) {
|
||||||
@@ -83,10 +108,17 @@ function createMapStateToProps() {
|
|||||||
isAlbumsPopulated,
|
isAlbumsPopulated,
|
||||||
albumsError,
|
albumsError,
|
||||||
hasAlbums,
|
hasAlbums,
|
||||||
hasMonitoredAlbums,
|
hasMonitoredAlbums
|
||||||
albumTypes
|
|
||||||
} = albums;
|
} = albums;
|
||||||
|
|
||||||
|
const {
|
||||||
|
isSeriesFetching,
|
||||||
|
isSeriesPopulated,
|
||||||
|
seriesError,
|
||||||
|
hasSeries,
|
||||||
|
series: seriesItems
|
||||||
|
} = series;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isTrackFilesFetching,
|
isTrackFilesFetching,
|
||||||
isTrackFilesPopulated,
|
isTrackFilesPopulated,
|
||||||
@@ -94,28 +126,26 @@ function createMapStateToProps() {
|
|||||||
hasTrackFiles
|
hasTrackFiles
|
||||||
} = trackFiles;
|
} = trackFiles;
|
||||||
|
|
||||||
const sortedAlbumTypes = _.orderBy(albumTypes);
|
|
||||||
|
|
||||||
const previousArtist = sortedArtist[artistIndex - 1] || _.last(sortedArtist);
|
const previousArtist = sortedArtist[artistIndex - 1] || _.last(sortedArtist);
|
||||||
const nextArtist = sortedArtist[artistIndex + 1] || _.first(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 artistRefreshingCommand = findCommand(commands, { name: commandNames.REFRESH_ARTIST });
|
||||||
const allArtistRefreshing = (
|
const allArtistRefreshing = (
|
||||||
isCommandExecuting(artistRefreshingCommand) &&
|
isCommandExecuting(artistRefreshingCommand) &&
|
||||||
!artistRefreshingCommand.body.artistId
|
!artistRefreshingCommand.body.authorId
|
||||||
);
|
);
|
||||||
const isRefreshing = isArtistRefreshing || allArtistRefreshing;
|
const isRefreshing = isArtistRefreshing || allArtistRefreshing;
|
||||||
const isSearching = isCommandExecuting(findCommand(commands, { name: commandNames.ARTIST_SEARCH, artistId: artist.id }));
|
const isSearching = isCommandExecuting(findCommand(commands, { name: commandNames.ARTIST_SEARCH, authorId: artist.id }));
|
||||||
const isRenamingFiles = isCommandExecuting(findCommand(commands, { name: commandNames.RENAME_FILES, artistId: artist.id }));
|
const isRenamingFiles = isCommandExecuting(findCommand(commands, { name: commandNames.RENAME_FILES, authorId: artist.id }));
|
||||||
|
|
||||||
const isRenamingArtistCommand = findCommand(commands, { name: commandNames.RENAME_ARTIST });
|
const isRenamingArtistCommand = findCommand(commands, { name: commandNames.RENAME_ARTIST });
|
||||||
const isRenamingArtist = (
|
const isRenamingArtist = (
|
||||||
isCommandExecuting(isRenamingArtistCommand) &&
|
isCommandExecuting(isRenamingArtistCommand) &&
|
||||||
isRenamingArtistCommand.body.artistIds.indexOf(artist.id) > -1
|
isRenamingArtistCommand.body.authorIds.indexOf(artist.id) > -1
|
||||||
);
|
);
|
||||||
|
|
||||||
const isFetching = isAlbumsFetching || isTrackFilesFetching;
|
const isFetching = isAlbumsFetching || isSeriesFetching || isTrackFilesFetching;
|
||||||
const isPopulated = isAlbumsPopulated && isTrackFilesPopulated;
|
const isPopulated = isAlbumsPopulated && isSeriesPopulated && isTrackFilesPopulated;
|
||||||
|
|
||||||
const alternateTitles = _.reduce(artist.alternateTitles, (acc, alternateTitle) => {
|
const alternateTitles = _.reduce(artist.alternateTitles, (acc, alternateTitle) => {
|
||||||
if ((alternateTitle.seasonNumber === -1 || alternateTitle.seasonNumber === undefined) &&
|
if ((alternateTitle.seasonNumber === -1 || alternateTitle.seasonNumber === undefined) &&
|
||||||
@@ -128,7 +158,6 @@ function createMapStateToProps() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...artist,
|
...artist,
|
||||||
albumTypes: sortedAlbumTypes,
|
|
||||||
alternateTitles,
|
alternateTitles,
|
||||||
isArtistRefreshing,
|
isArtistRefreshing,
|
||||||
allArtistRefreshing,
|
allArtistRefreshing,
|
||||||
@@ -139,9 +168,12 @@ function createMapStateToProps() {
|
|||||||
isFetching,
|
isFetching,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
albumsError,
|
albumsError,
|
||||||
|
seriesError,
|
||||||
trackFilesError,
|
trackFilesError,
|
||||||
hasAlbums,
|
hasAlbums,
|
||||||
hasMonitoredAlbums,
|
hasMonitoredAlbums,
|
||||||
|
hasSeries,
|
||||||
|
series: seriesItems,
|
||||||
hasTrackFiles,
|
hasTrackFiles,
|
||||||
previousArtist,
|
previousArtist,
|
||||||
nextArtist
|
nextArtist
|
||||||
@@ -153,11 +185,15 @@ function createMapStateToProps() {
|
|||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
fetchAlbums,
|
fetchAlbums,
|
||||||
clearAlbums,
|
clearAlbums,
|
||||||
|
fetchSeries,
|
||||||
|
clearSeries,
|
||||||
fetchTrackFiles,
|
fetchTrackFiles,
|
||||||
clearTrackFiles,
|
clearTrackFiles,
|
||||||
toggleArtistMonitored,
|
toggleArtistMonitored,
|
||||||
fetchQueueDetails,
|
fetchQueueDetails,
|
||||||
clearQueueDetails,
|
clearQueueDetails,
|
||||||
|
clearReleases,
|
||||||
|
cancelFetchReleases,
|
||||||
executeCommand
|
executeCommand
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -207,17 +243,21 @@ class ArtistDetailsConnector extends Component {
|
|||||||
// Control
|
// Control
|
||||||
|
|
||||||
populate = () => {
|
populate = () => {
|
||||||
const artistId = this.props.id;
|
const authorId = this.props.id;
|
||||||
|
|
||||||
this.props.fetchAlbums({ artistId });
|
this.props.fetchAlbums({ authorId });
|
||||||
this.props.fetchTrackFiles({ artistId });
|
this.props.fetchSeries({ authorId });
|
||||||
this.props.fetchQueueDetails({ artistId });
|
this.props.fetchTrackFiles({ authorId });
|
||||||
|
this.props.fetchQueueDetails({ authorId });
|
||||||
}
|
}
|
||||||
|
|
||||||
unpopulate = () => {
|
unpopulate = () => {
|
||||||
|
this.props.cancelFetchReleases();
|
||||||
this.props.clearAlbums();
|
this.props.clearAlbums();
|
||||||
|
this.props.clearSeries();
|
||||||
this.props.clearTrackFiles();
|
this.props.clearTrackFiles();
|
||||||
this.props.clearQueueDetails();
|
this.props.clearQueueDetails();
|
||||||
|
this.props.clearReleases();
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -225,7 +265,7 @@ class ArtistDetailsConnector extends Component {
|
|||||||
|
|
||||||
onMonitorTogglePress = (monitored) => {
|
onMonitorTogglePress = (monitored) => {
|
||||||
this.props.toggleArtistMonitored({
|
this.props.toggleArtistMonitored({
|
||||||
artistId: this.props.id,
|
authorId: this.props.id,
|
||||||
monitored
|
monitored
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -233,14 +273,14 @@ class ArtistDetailsConnector extends Component {
|
|||||||
onRefreshPress = () => {
|
onRefreshPress = () => {
|
||||||
this.props.executeCommand({
|
this.props.executeCommand({
|
||||||
name: commandNames.REFRESH_ARTIST,
|
name: commandNames.REFRESH_ARTIST,
|
||||||
artistId: this.props.id
|
authorId: this.props.id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onSearchPress = () => {
|
onSearchPress = () => {
|
||||||
this.props.executeCommand({
|
this.props.executeCommand({
|
||||||
name: commandNames.ARTIST_SEARCH,
|
name: commandNames.ARTIST_SEARCH,
|
||||||
artistId: this.props.id
|
authorId: this.props.id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,7 +301,7 @@ class ArtistDetailsConnector extends Component {
|
|||||||
|
|
||||||
ArtistDetailsConnector.propTypes = {
|
ArtistDetailsConnector.propTypes = {
|
||||||
id: PropTypes.number.isRequired,
|
id: PropTypes.number.isRequired,
|
||||||
foreignArtistId: PropTypes.string.isRequired,
|
titleSlug: PropTypes.string.isRequired,
|
||||||
isArtistRefreshing: PropTypes.bool.isRequired,
|
isArtistRefreshing: PropTypes.bool.isRequired,
|
||||||
allArtistRefreshing: PropTypes.bool.isRequired,
|
allArtistRefreshing: PropTypes.bool.isRequired,
|
||||||
isRefreshing: PropTypes.bool.isRequired,
|
isRefreshing: PropTypes.bool.isRequired,
|
||||||
@@ -269,11 +309,15 @@ ArtistDetailsConnector.propTypes = {
|
|||||||
isRenamingArtist: PropTypes.bool.isRequired,
|
isRenamingArtist: PropTypes.bool.isRequired,
|
||||||
fetchAlbums: PropTypes.func.isRequired,
|
fetchAlbums: PropTypes.func.isRequired,
|
||||||
clearAlbums: PropTypes.func.isRequired,
|
clearAlbums: PropTypes.func.isRequired,
|
||||||
|
fetchSeries: PropTypes.func.isRequired,
|
||||||
|
clearSeries: PropTypes.func.isRequired,
|
||||||
fetchTrackFiles: PropTypes.func.isRequired,
|
fetchTrackFiles: PropTypes.func.isRequired,
|
||||||
clearTrackFiles: PropTypes.func.isRequired,
|
clearTrackFiles: PropTypes.func.isRequired,
|
||||||
toggleArtistMonitored: PropTypes.func.isRequired,
|
toggleArtistMonitored: PropTypes.func.isRequired,
|
||||||
fetchQueueDetails: PropTypes.func.isRequired,
|
fetchQueueDetails: PropTypes.func.isRequired,
|
||||||
clearQueueDetails: PropTypes.func.isRequired,
|
clearQueueDetails: PropTypes.func.isRequired,
|
||||||
|
clearReleases: PropTypes.func.isRequired,
|
||||||
|
cancelFetchReleases: PropTypes.func.isRequired,
|
||||||
executeCommand: PropTypes.func.isRequired
|
executeCommand: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,26 +7,12 @@ import styles from './ArtistDetailsLinks.css';
|
|||||||
|
|
||||||
function ArtistDetailsLinks(props) {
|
function ArtistDetailsLinks(props) {
|
||||||
const {
|
const {
|
||||||
foreignArtistId,
|
|
||||||
links
|
links
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.links}>
|
<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) => {
|
{links.map((link, index) => {
|
||||||
return (
|
return (
|
||||||
<span key={index}>
|
<span key={index}>
|
||||||
@@ -56,7 +42,6 @@ function ArtistDetailsLinks(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ArtistDetailsLinks.propTypes = {
|
ArtistDetailsLinks.propTypes = {
|
||||||
foreignArtistId: PropTypes.string.isRequired,
|
|
||||||
links: PropTypes.arrayOf(PropTypes.object).isRequired
|
links: PropTypes.arrayOf(PropTypes.object).isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ function createMapStateToProps() {
|
|||||||
(state, { match }) => match,
|
(state, { match }) => match,
|
||||||
(state) => state.artist,
|
(state) => state.artist,
|
||||||
(match, artist) => {
|
(match, artist) => {
|
||||||
const foreignArtistId = match.params.foreignArtistId;
|
const titleSlug = match.params.titleSlug;
|
||||||
const {
|
const {
|
||||||
isFetching,
|
isFetching,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
@@ -25,13 +25,13 @@ function createMapStateToProps() {
|
|||||||
items
|
items
|
||||||
} = artist;
|
} = artist;
|
||||||
|
|
||||||
const artistIndex = _.findIndex(items, { foreignArtistId });
|
const artistIndex = _.findIndex(items, { titleSlug });
|
||||||
|
|
||||||
if (artistIndex > -1) {
|
if (artistIndex > -1) {
|
||||||
return {
|
return {
|
||||||
isFetching,
|
isFetching,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
foreignArtistId
|
titleSlug
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ class ArtistDetailsPageConnector extends Component {
|
|||||||
// Lifecycle
|
// Lifecycle
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (!this.props.foreignArtistId) {
|
if (!this.props.titleSlug) {
|
||||||
this.props.push(`${window.Readarr.urlBase}/`);
|
this.props.push(`${window.Readarr.urlBase}/`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,7 @@ class ArtistDetailsPageConnector extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
foreignArtistId,
|
titleSlug,
|
||||||
isFetching,
|
isFetching,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
error
|
error
|
||||||
@@ -84,33 +84,33 @@ class ArtistDetailsPageConnector extends Component {
|
|||||||
if (!isFetching && !!error) {
|
if (!isFetching && !!error) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.errorMessage}>
|
<div className={styles.errorMessage}>
|
||||||
{getErrorMessage(error, 'Failed to load artist from API')}
|
{getErrorMessage(error, 'Failed to load author from API')}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!foreignArtistId) {
|
if (!titleSlug) {
|
||||||
return (
|
return (
|
||||||
<NotFound
|
<NotFound
|
||||||
message="Sorry, that artist cannot be found."
|
message="Sorry, that author cannot be found."
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ArtistDetailsConnector
|
<ArtistDetailsConnector
|
||||||
foreignArtistId={foreignArtistId}
|
titleSlug={titleSlug}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ArtistDetailsPageConnector.propTypes = {
|
ArtistDetailsPageConnector.propTypes = {
|
||||||
foreignArtistId: PropTypes.string,
|
titleSlug: PropTypes.string,
|
||||||
isFetching: PropTypes.bool.isRequired,
|
isFetching: PropTypes.bool.isRequired,
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
error: PropTypes.object,
|
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
|
push: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,9 @@ import _ from 'lodash';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import getToggledRange from 'Utilities/Table/getToggledRange';
|
import getToggledRange from 'Utilities/Table/getToggledRange';
|
||||||
import { icons, sortDirections } from 'Helpers/Props';
|
import { 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 Table from 'Components/Table/Table';
|
||||||
import TableBody from 'Components/Table/TableBody';
|
import TableBody from 'Components/Table/TableBody';
|
||||||
import TrackFileEditorModal from 'TrackFile/Editor/TrackFileEditorModal';
|
|
||||||
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
|
|
||||||
import AlbumRowConnector from './AlbumRowConnector';
|
import AlbumRowConnector from './AlbumRowConnector';
|
||||||
import styles from './ArtistDetailsSeason.css';
|
import styles from './ArtistDetailsSeason.css';
|
||||||
|
|
||||||
@@ -22,92 +17,29 @@ class ArtistDetailsSeason extends Component {
|
|||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isOrganizeModalOpen: false,
|
|
||||||
isManageTracksOpen: false,
|
|
||||||
lastToggledAlbum: null
|
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
|
// Listeners
|
||||||
|
|
||||||
onOrganizePress = () => {
|
onMonitorAlbumPress = (bookId, monitored, { shiftKey }) => {
|
||||||
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 }) => {
|
|
||||||
const lastToggled = this.state.lastToggledAlbum;
|
const lastToggled = this.state.lastToggledAlbum;
|
||||||
const albumIds = [albumId];
|
const bookIds = [bookId];
|
||||||
|
|
||||||
if (shiftKey && lastToggled) {
|
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;
|
const items = this.props.items;
|
||||||
|
|
||||||
for (let i = lower; i < upper; i++) {
|
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() {
|
render() {
|
||||||
const {
|
const {
|
||||||
artistId,
|
|
||||||
label,
|
|
||||||
items,
|
items,
|
||||||
columns,
|
columns,
|
||||||
isExpanded,
|
|
||||||
sortKey,
|
sortKey,
|
||||||
sortDirection,
|
sortDirection,
|
||||||
onSortPress,
|
onSortPress,
|
||||||
isSmallScreen,
|
|
||||||
onTableOptionChange
|
onTableOptionChange
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
|
||||||
isOrganizeModalOpen,
|
|
||||||
isManageTracksOpen
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={styles.albumType}
|
className={styles.albumType}
|
||||||
>
|
>
|
||||||
<Link
|
<div className={styles.albums}>
|
||||||
className={styles.expandButton}
|
<Table
|
||||||
onPress={this.onExpandPress}
|
columns={columns}
|
||||||
>
|
sortKey={sortKey}
|
||||||
<div className={styles.header}>
|
sortDirection={sortDirection}
|
||||||
<div className={styles.left}>
|
onSortPress={onSortPress}
|
||||||
|
onTableOptionChange={onTableOptionChange}
|
||||||
|
>
|
||||||
|
<TableBody>
|
||||||
{
|
{
|
||||||
<div>
|
items.map((item) => {
|
||||||
<span className={styles.albumTypeLabel}>
|
return (
|
||||||
{label}
|
<AlbumRowConnector
|
||||||
</span>
|
key={item.id}
|
||||||
|
|
||||||
<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
|
|
||||||
columns={columns}
|
columns={columns}
|
||||||
sortKey={sortKey}
|
{...item}
|
||||||
sortDirection={sortDirection}
|
onMonitorAlbumPress={this.onMonitorAlbumPress}
|
||||||
onSortPress={onSortPress}
|
/>
|
||||||
onTableOptionChange={onTableOptionChange}
|
);
|
||||||
>
|
})
|
||||||
<TableBody>
|
}
|
||||||
{
|
</TableBody>
|
||||||
items.map((item) => {
|
</Table>
|
||||||
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>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<OrganizePreviewModalConnector
|
|
||||||
isOpen={isOrganizeModalOpen}
|
|
||||||
artistId={artistId}
|
|
||||||
onModalClose={this.onOrganizeModalClose}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TrackFileEditorModal
|
|
||||||
isOpen={isManageTracksOpen}
|
|
||||||
artistId={artistId}
|
|
||||||
onModalClose={this.onManageTracksModalClose}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ArtistDetailsSeason.propTypes = {
|
ArtistDetailsSeason.propTypes = {
|
||||||
artistId: PropTypes.number.isRequired,
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
label: PropTypes.string.isRequired,
|
|
||||||
sortKey: PropTypes.string,
|
sortKey: PropTypes.string,
|
||||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
sortDirection: PropTypes.oneOf(sortDirections.all),
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
isExpanded: PropTypes.bool,
|
|
||||||
isSmallScreen: PropTypes.bool.isRequired,
|
|
||||||
onTableOptionChange: PropTypes.func.isRequired,
|
onTableOptionChange: PropTypes.func.isRequired,
|
||||||
onExpandPress: PropTypes.func.isRequired,
|
onExpandPress: PropTypes.func.isRequired,
|
||||||
onSortPress: PropTypes.func.isRequired,
|
onSortPress: PropTypes.func.isRequired,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ function createMapStateToProps() {
|
|||||||
createUISettingsSelector(),
|
createUISettingsSelector(),
|
||||||
(label, albums, artist, commands, dimensions, uiSettings) => {
|
(label, albums, artist, commands, dimensions, uiSettings) => {
|
||||||
|
|
||||||
const albumsInGroup = _.filter(albums.items, { albumType: label });
|
const albumsInGroup = albums.items;
|
||||||
|
|
||||||
let sortDir = 'asc';
|
let sortDir = 'asc';
|
||||||
|
|
||||||
@@ -66,9 +66,9 @@ class ArtistDetailsSeasonConnector extends Component {
|
|||||||
this.props.dispatchSetAlbumSort({ sortKey });
|
this.props.dispatchSetAlbumSort({ sortKey });
|
||||||
}
|
}
|
||||||
|
|
||||||
onMonitorAlbumPress = (albumIds, monitored) => {
|
onMonitorAlbumPress = (bookIds, monitored) => {
|
||||||
this.props.toggleAlbumsMonitored({
|
this.props.toggleAlbumsMonitored({
|
||||||
albumIds,
|
bookIds,
|
||||||
monitored
|
monitored
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,7 @@ class ArtistDetailsSeasonConnector extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ArtistDetailsSeasonConnector.propTypes = {
|
ArtistDetailsSeasonConnector.propTypes = {
|
||||||
artistId: PropTypes.number.isRequired,
|
authorId: PropTypes.number.isRequired,
|
||||||
toggleAlbumsMonitored: PropTypes.func.isRequired,
|
toggleAlbumsMonitored: PropTypes.func.isRequired,
|
||||||
setAlbumsTableOption: PropTypes.func.isRequired,
|
setAlbumsTableOption: PropTypes.func.isRequired,
|
||||||
dispatchSetAlbumSort: PropTypes.func.isRequired,
|
dispatchSetAlbumSort: PropTypes.func.isRequired,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
.medium {
|
.albumType {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
border: 1px solid $borderColor;
|
border: 1px solid $borderColor;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@@ -15,31 +15,36 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mediumNumber {
|
.albumTypeLabel {
|
||||||
margin-right: 10px;
|
margin-right: 5px;
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mediumFormat {
|
.albumCount {
|
||||||
color: #8895aa;
|
color: #8895aa;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.episodeCountTooltip {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
.expandButton {
|
.expandButton {
|
||||||
composes: link from '~Components/Link/Link.css';
|
composes: link from '~Components/Link/Link.css';
|
||||||
|
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
margin: 0 20px;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left {
|
.left {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex: 0 1 300px;
|
flex: 1 1 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left,
|
.left,
|
||||||
@@ -57,7 +62,7 @@
|
|||||||
composes: menuContent from '~Components/Menu/MenuContent.css';
|
composes: menuContent from '~Components/Menu/MenuContent.css';
|
||||||
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
font-size: 14px;
|
font-size: $defaultFontSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actionMenuIcon {
|
.actionMenuIcon {
|
||||||
@@ -70,38 +75,46 @@
|
|||||||
width: 30px;
|
width: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tracks {
|
.albums {
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
border-top: 1px solid $borderColor;
|
border-top: 1px solid $borderColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapseButtonContainer {
|
.collapseButtonContainer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-top: 1px solid $borderColor;
|
border-top: 1px solid $borderColor;
|
||||||
border-bottom-right-radius: 4px;
|
border-bottom-right-radius: 4px;
|
||||||
border-bottom-left-radius: 4px;
|
border-bottom-left-radius: 4px;
|
||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
text-align: center;
|
}
|
||||||
|
|
||||||
|
.collapseButtonIcon {
|
||||||
|
margin-bottom: -4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.expandButtonIcon {
|
.expandButtonIcon {
|
||||||
composes: actionButton;
|
composes: actionButton;
|
||||||
|
|
||||||
position: absolute;
|
margin-right: 15px;
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
/* position: absolute; */
|
||||||
margin-top: -12px;
|
/* top: 50%; */
|
||||||
margin-left: -15px;
|
/* left: 90%; */
|
||||||
|
/* margin-top: -12px; */
|
||||||
|
/* margin-left: -15px; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.noTracks {
|
.noAlbums {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: $breakpointSmall) {
|
@media only screen and (max-width: $breakpointSmall) {
|
||||||
.medium {
|
.albumType {
|
||||||
border-right: 0;
|
border-right: 0;
|
||||||
border-left: 0;
|
border-left: 0;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
205
frontend/src/Artist/Details/AuthorDetailsSeries.js
Normal file
@@ -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;
|
||||||
121
frontend/src/Artist/Details/AuthorDetailsSeriesConnector.js
Normal file
@@ -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 {
|
const {
|
||||||
monitored,
|
monitored,
|
||||||
albumFolder,
|
|
||||||
qualityProfileId,
|
qualityProfileId,
|
||||||
metadataProfileId,
|
metadataProfileId,
|
||||||
path,
|
path,
|
||||||
@@ -99,18 +98,6 @@ class EditArtistModalContent extends Component {
|
|||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>Use Album Folder</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.CHECK}
|
|
||||||
name="albumFolder"
|
|
||||||
helpText="Sort tracks into album folders"
|
|
||||||
{...albumFolder}
|
|
||||||
onChange={onInputChange}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>Quality Profile</FormLabel>
|
<FormLabel>Quality Profile</FormLabel>
|
||||||
|
|
||||||
@@ -213,7 +200,7 @@ class EditArtistModalContent extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EditArtistModalContent.propTypes = {
|
EditArtistModalContent.propTypes = {
|
||||||
artistId: PropTypes.number.isRequired,
|
authorId: PropTypes.number.isRequired,
|
||||||
artistName: PropTypes.string.isRequired,
|
artistName: PropTypes.string.isRequired,
|
||||||
item: PropTypes.object.isRequired,
|
item: PropTypes.object.isRequired,
|
||||||
isSaving: PropTypes.bool.isRequired,
|
isSaving: PropTypes.bool.isRequired,
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ class EditArtistModalContentConnector extends Component {
|
|||||||
|
|
||||||
onSavePress = (moveFiles) => {
|
onSavePress = (moveFiles) => {
|
||||||
this.props.dispatchSaveArtist({
|
this.props.dispatchSaveArtist({
|
||||||
id: this.props.artistId,
|
id: this.props.authorId,
|
||||||
moveFiles
|
moveFiles
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -108,7 +108,7 @@ class EditArtistModalContentConnector extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EditArtistModalContentConnector.propTypes = {
|
EditArtistModalContentConnector.propTypes = {
|
||||||
artistId: PropTypes.number,
|
authorId: PropTypes.number,
|
||||||
isSaving: PropTypes.bool.isRequired,
|
isSaving: PropTypes.bool.isRequired,
|
||||||
saveError: PropTypes.object,
|
saveError: PropTypes.object,
|
||||||
dispatchSetArtistValue: PropTypes.func.isRequired,
|
dispatchSetArtistValue: PropTypes.func.isRequired,
|
||||||
|
|||||||
@@ -45,12 +45,6 @@ function getColumns(showMetadataProfile) {
|
|||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: showMetadataProfile
|
isVisible: showMetadataProfile
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'albumFolder',
|
|
||||||
label: 'Album Folder',
|
|
||||||
isSortable: true,
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'path',
|
name: 'path',
|
||||||
label: 'Path',
|
label: 'Path',
|
||||||
@@ -122,7 +116,7 @@ class ArtistEditor extends Component {
|
|||||||
|
|
||||||
onSaveSelected = (changes) => {
|
onSaveSelected = (changes) => {
|
||||||
this.props.onSaveSelected({
|
this.props.onSaveSelected({
|
||||||
artistIds: this.getSelectedIds(),
|
authorIds: this.getSelectedIds(),
|
||||||
...changes
|
...changes
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -184,7 +178,7 @@ class ArtistEditor extends Component {
|
|||||||
columns
|
columns
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const selectedArtistIds = this.getSelectedIds();
|
const selectedAuthorIds = this.getSelectedIds();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContent title="Artist Editor">
|
<PageContent title="Artist Editor">
|
||||||
@@ -252,8 +246,8 @@ class ArtistEditor extends Component {
|
|||||||
</PageContentBodyConnector>
|
</PageContentBodyConnector>
|
||||||
|
|
||||||
<ArtistEditorFooter
|
<ArtistEditorFooter
|
||||||
artistIds={selectedArtistIds}
|
authorIds={selectedAuthorIds}
|
||||||
selectedCount={selectedArtistIds.length}
|
selectedCount={selectedAuthorIds.length}
|
||||||
isSaving={isSaving}
|
isSaving={isSaving}
|
||||||
saveError={saveError}
|
saveError={saveError}
|
||||||
isDeleting={isDeleting}
|
isDeleting={isDeleting}
|
||||||
@@ -268,13 +262,13 @@ class ArtistEditor extends Component {
|
|||||||
|
|
||||||
<OrganizeArtistModal
|
<OrganizeArtistModal
|
||||||
isOpen={this.state.isOrganizingArtistModalOpen}
|
isOpen={this.state.isOrganizingArtistModalOpen}
|
||||||
artistIds={selectedArtistIds}
|
authorIds={selectedAuthorIds}
|
||||||
onModalClose={this.onOrganizeArtistModalClose}
|
onModalClose={this.onOrganizeArtistModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<RetagArtistModal
|
<RetagArtistModal
|
||||||
isOpen={this.state.isRetaggingArtistModalOpen}
|
isOpen={this.state.isRetaggingArtistModalOpen}
|
||||||
artistIds={selectedArtistIds}
|
authorIds={selectedAuthorIds}
|
||||||
onModalClose={this.onRetagArtistModalClose}
|
onModalClose={this.onRetagArtistModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ class ArtistEditorFooter extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
artistIds,
|
authorIds,
|
||||||
selectedCount,
|
selectedCount,
|
||||||
isSaving,
|
isSaving,
|
||||||
isDeleting,
|
isDeleting,
|
||||||
@@ -309,14 +309,14 @@ class ArtistEditorFooter extends Component {
|
|||||||
|
|
||||||
<TagsModal
|
<TagsModal
|
||||||
isOpen={isTagsModalOpen}
|
isOpen={isTagsModalOpen}
|
||||||
artistIds={artistIds}
|
authorIds={authorIds}
|
||||||
onApplyTagsPress={this.onApplyTagsPress}
|
onApplyTagsPress={this.onApplyTagsPress}
|
||||||
onModalClose={this.onTagsModalClose}
|
onModalClose={this.onTagsModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeleteArtistModal
|
<DeleteArtistModal
|
||||||
isOpen={isDeleteArtistModalOpen}
|
isOpen={isDeleteArtistModalOpen}
|
||||||
artistIds={artistIds}
|
authorIds={authorIds}
|
||||||
onModalClose={this.onDeleteArtistModalClose}
|
onModalClose={this.onDeleteArtistModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -333,7 +333,7 @@ class ArtistEditorFooter extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ArtistEditorFooter.propTypes = {
|
ArtistEditorFooter.propTypes = {
|
||||||
artistIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
authorIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
selectedCount: PropTypes.number.isRequired,
|
selectedCount: PropTypes.number.isRequired,
|
||||||
isSaving: PropTypes.bool.isRequired,
|
isSaving: PropTypes.bool.isRequired,
|
||||||
saveError: PropTypes.object,
|
saveError: PropTypes.object,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import _ from 'lodash';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import TagListConnector from 'Components/TagListConnector';
|
import TagListConnector from 'Components/TagListConnector';
|
||||||
import CheckInput from 'Components/Form/CheckInput';
|
|
||||||
import TableRow from 'Components/Table/TableRow';
|
import TableRow from 'Components/Table/TableRow';
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
||||||
@@ -27,13 +26,12 @@ class ArtistEditorRow extends Component {
|
|||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
status,
|
status,
|
||||||
foreignArtistId,
|
titleSlug,
|
||||||
artistName,
|
artistName,
|
||||||
artistType,
|
artistType,
|
||||||
monitored,
|
monitored,
|
||||||
metadataProfile,
|
metadataProfile,
|
||||||
qualityProfile,
|
qualityProfile,
|
||||||
albumFolder,
|
|
||||||
path,
|
path,
|
||||||
tags,
|
tags,
|
||||||
columns,
|
columns,
|
||||||
@@ -57,7 +55,7 @@ class ArtistEditorRow extends Component {
|
|||||||
|
|
||||||
<TableRowCell className={styles.title}>
|
<TableRowCell className={styles.title}>
|
||||||
<ArtistNameLink
|
<ArtistNameLink
|
||||||
foreignArtistId={foreignArtistId}
|
titleSlug={titleSlug}
|
||||||
artistName={artistName}
|
artistName={artistName}
|
||||||
/>
|
/>
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
@@ -73,15 +71,6 @@ class ArtistEditorRow extends Component {
|
|||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
}
|
}
|
||||||
|
|
||||||
<TableRowCell className={styles.albumFolder}>
|
|
||||||
<CheckInput
|
|
||||||
name="albumFolder"
|
|
||||||
value={albumFolder}
|
|
||||||
isDisabled={true}
|
|
||||||
onChange={this.onAlbumFolderChange}
|
|
||||||
/>
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell>
|
<TableRowCell>
|
||||||
{path}
|
{path}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
@@ -99,13 +88,12 @@ class ArtistEditorRow extends Component {
|
|||||||
ArtistEditorRow.propTypes = {
|
ArtistEditorRow.propTypes = {
|
||||||
id: PropTypes.number.isRequired,
|
id: PropTypes.number.isRequired,
|
||||||
status: PropTypes.string.isRequired,
|
status: PropTypes.string.isRequired,
|
||||||
foreignArtistId: PropTypes.string.isRequired,
|
titleSlug: PropTypes.string.isRequired,
|
||||||
artistName: PropTypes.string.isRequired,
|
artistName: PropTypes.string.isRequired,
|
||||||
artistType: PropTypes.string,
|
artistType: PropTypes.string,
|
||||||
monitored: PropTypes.bool.isRequired,
|
monitored: PropTypes.bool.isRequired,
|
||||||
metadataProfile: PropTypes.object.isRequired,
|
metadataProfile: PropTypes.object.isRequired,
|
||||||
qualityProfile: PropTypes.object.isRequired,
|
qualityProfile: PropTypes.object.isRequired,
|
||||||
albumFolder: PropTypes.bool.isRequired,
|
|
||||||
path: PropTypes.string.isRequired,
|
path: PropTypes.string.isRequired,
|
||||||
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
|
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ import RetagArtistModalContent from './RetagArtistModalContent';
|
|||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state, { artistIds }) => artistIds,
|
(state, { authorIds }) => authorIds,
|
||||||
createAllArtistSelector(),
|
createAllArtistSelector(),
|
||||||
(artistIds, allArtists) => {
|
(authorIds, allArtists) => {
|
||||||
const artist = _.intersectionWith(allArtists, artistIds, (s, id) => {
|
const artist = _.intersectionWith(allArtists, authorIds, (s, id) => {
|
||||||
return s.id === id;
|
return s.id === id;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ class RetagArtistModalContentConnector extends Component {
|
|||||||
onRetagArtistPress = () => {
|
onRetagArtistPress = () => {
|
||||||
this.props.executeCommand({
|
this.props.executeCommand({
|
||||||
name: commandNames.RETAG_ARTIST,
|
name: commandNames.RETAG_ARTIST,
|
||||||
artistIds: this.props.artistIds
|
authorIds: this.props.authorIds
|
||||||
});
|
});
|
||||||
|
|
||||||
this.props.onModalClose(true);
|
this.props.onModalClose(true);
|
||||||
@@ -59,7 +59,7 @@ class RetagArtistModalContentConnector extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
RetagArtistModalContentConnector.propTypes = {
|
RetagArtistModalContentConnector.propTypes = {
|
||||||
artistIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
authorIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired,
|
onModalClose: PropTypes.func.isRequired,
|
||||||
executeCommand: PropTypes.func.isRequired
|
executeCommand: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import DeleteArtistModalContent from './DeleteArtistModalContent';
|
|||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state, { artistIds }) => artistIds,
|
(state, { authorIds }) => authorIds,
|
||||||
createAllArtistSelector(),
|
createAllArtistSelector(),
|
||||||
(artistIds, allArtists) => {
|
(authorIds, allArtists) => {
|
||||||
const selectedArtist = _.intersectionWith(allArtists, artistIds, (s, id) => {
|
const selectedArtist = _.intersectionWith(allArtists, authorIds, (s, id) => {
|
||||||
return s.id === id;
|
return s.id === id;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
return {
|
return {
|
||||||
onDeleteSelectedPress(deleteFiles) {
|
onDeleteSelectedPress(deleteFiles) {
|
||||||
dispatch(bulkDeleteArtist({
|
dispatch(bulkDeleteArtist({
|
||||||
artistIds: props.artistIds,
|
authorIds: props.authorIds,
|
||||||
deleteFiles
|
deleteFiles
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ import OrganizeArtistModalContent from './OrganizeArtistModalContent';
|
|||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state, { artistIds }) => artistIds,
|
(state, { authorIds }) => authorIds,
|
||||||
createAllArtistSelector(),
|
createAllArtistSelector(),
|
||||||
(artistIds, allArtists) => {
|
(authorIds, allArtists) => {
|
||||||
const artist = _.intersectionWith(allArtists, artistIds, (s, id) => {
|
const artist = _.intersectionWith(allArtists, authorIds, (s, id) => {
|
||||||
return s.id === id;
|
return s.id === id;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ class OrganizeArtistModalContentConnector extends Component {
|
|||||||
onOrganizeArtistPress = () => {
|
onOrganizeArtistPress = () => {
|
||||||
this.props.executeCommand({
|
this.props.executeCommand({
|
||||||
name: commandNames.RENAME_ARTIST,
|
name: commandNames.RENAME_ARTIST,
|
||||||
artistIds: this.props.artistIds
|
authorIds: this.props.authorIds
|
||||||
});
|
});
|
||||||
|
|
||||||
this.props.onModalClose(true);
|
this.props.onModalClose(true);
|
||||||
@@ -59,7 +59,7 @@ class OrganizeArtistModalContentConnector extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
OrganizeArtistModalContentConnector.propTypes = {
|
OrganizeArtistModalContentConnector.propTypes = {
|
||||||
artistIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
authorIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired,
|
onModalClose: PropTypes.func.isRequired,
|
||||||
executeCommand: PropTypes.func.isRequired
|
executeCommand: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ import TagsModalContent from './TagsModalContent';
|
|||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state, { artistIds }) => artistIds,
|
(state, { authorIds }) => authorIds,
|
||||||
createAllArtistSelector(),
|
createAllArtistSelector(),
|
||||||
createTagsSelector(),
|
createTagsSelector(),
|
||||||
(artistIds, allArtists, tagList) => {
|
(authorIds, allArtists, tagList) => {
|
||||||
const artist = _.intersectionWith(allArtists, artistIds, (s, id) => {
|
const artist = _.intersectionWith(allArtists, authorIds, (s, id) => {
|
||||||
return s.id === id;
|
return s.id === id;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import React, { Component } from 'react';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { fetchArtistHistory, clearArtistHistory, artistHistoryMarkAsFailed } from 'Store/Actions/artistHistoryActions';
|
import { fetchArtistHistory, clearArtistHistory, artistHistoryMarkAsFailed } from 'Store/Actions/artistHistoryActions';
|
||||||
import ArtistHistoryModalContent from './ArtistHistoryModalContent';
|
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
@@ -20,20 +19,20 @@ const mapDispatchToProps = {
|
|||||||
artistHistoryMarkAsFailed
|
artistHistoryMarkAsFailed
|
||||||
};
|
};
|
||||||
|
|
||||||
class ArtistHistoryModalContentConnector extends Component {
|
class ArtistHistoryContentConnector extends Component {
|
||||||
|
|
||||||
//
|
//
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const {
|
const {
|
||||||
artistId,
|
authorId,
|
||||||
albumId
|
bookId
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
this.props.fetchArtistHistory({
|
this.props.fetchArtistHistory({
|
||||||
artistId,
|
authorId,
|
||||||
albumId
|
bookId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,14 +45,14 @@ class ArtistHistoryModalContentConnector extends Component {
|
|||||||
|
|
||||||
onMarkAsFailedPress = (historyId) => {
|
onMarkAsFailedPress = (historyId) => {
|
||||||
const {
|
const {
|
||||||
artistId,
|
authorId,
|
||||||
albumId
|
bookId
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
this.props.artistHistoryMarkAsFailed({
|
this.props.artistHistoryMarkAsFailed({
|
||||||
historyId,
|
historyId,
|
||||||
artistId,
|
authorId,
|
||||||
albumId
|
bookId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,21 +60,27 @@ class ArtistHistoryModalContentConnector extends Component {
|
|||||||
// Render
|
// Render
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const {
|
||||||
|
component: ViewComponent,
|
||||||
|
...otherProps
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ArtistHistoryModalContent
|
<ViewComponent
|
||||||
{...this.props}
|
{...otherProps}
|
||||||
onMarkAsFailedPress={this.onMarkAsFailedPress}
|
onMarkAsFailedPress={this.onMarkAsFailedPress}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ArtistHistoryModalContentConnector.propTypes = {
|
ArtistHistoryContentConnector.propTypes = {
|
||||||
artistId: PropTypes.number.isRequired,
|
component: PropTypes.elementType.isRequired,
|
||||||
albumId: PropTypes.number,
|
authorId: PropTypes.number.isRequired,
|
||||||
|
bookId: PropTypes.number,
|
||||||
fetchArtistHistory: PropTypes.func.isRequired,
|
fetchArtistHistory: PropTypes.func.isRequired,
|
||||||
clearArtistHistory: PropTypes.func.isRequired,
|
clearArtistHistory: PropTypes.func.isRequired,
|
||||||
artistHistoryMarkAsFailed: 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 PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Modal from 'Components/Modal/Modal';
|
import Modal from 'Components/Modal/Modal';
|
||||||
import ArtistHistoryModalContentConnector from './ArtistHistoryModalContentConnector';
|
import ArtistHistoryContentConnector from './ArtistHistoryContentConnector';
|
||||||
|
import ArtistHistoryModalContent from './ArtistHistoryModalContent';
|
||||||
|
|
||||||
function ArtistHistoryModal(props) {
|
function ArtistHistoryModal(props) {
|
||||||
const {
|
const {
|
||||||
@@ -15,7 +16,8 @@ function ArtistHistoryModal(props) {
|
|||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onModalClose={onModalClose}
|
onModalClose={onModalClose}
|
||||||
>
|
>
|
||||||
<ArtistHistoryModalContentConnector
|
<ArtistHistoryContentConnector
|
||||||
|
component={ArtistHistoryModalContent}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
onModalClose={onModalClose}
|
onModalClose={onModalClose}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,51 +1,11 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import Button from 'Components/Link/Button';
|
import Button from 'Components/Link/Button';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
|
||||||
import ModalContent from 'Components/Modal/ModalContent';
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
import ModalBody from 'Components/Modal/ModalBody';
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
import Table from 'Components/Table/Table';
|
import ArtistHistoryTableContent from './ArtistHistoryTableContent';
|
||||||
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 ArtistHistoryModalContent extends Component {
|
class ArtistHistoryModalContent extends Component {
|
||||||
|
|
||||||
@@ -54,18 +14,9 @@ class ArtistHistoryModalContent extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
albumId,
|
|
||||||
isFetching,
|
|
||||||
isPopulated,
|
|
||||||
error,
|
|
||||||
items,
|
|
||||||
onMarkAsFailedPress,
|
|
||||||
onModalClose
|
onModalClose
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const fullArtist = albumId == null;
|
|
||||||
const hasItems = !!items.length;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
@@ -73,40 +24,9 @@ class ArtistHistoryModalContent extends Component {
|
|||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
{
|
<ArtistHistoryTableContent
|
||||||
isFetching &&
|
{...this.props}
|
||||||
<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>
|
|
||||||
}
|
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
@@ -120,12 +40,6 @@ class ArtistHistoryModalContent extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ArtistHistoryModalContent.propTypes = {
|
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
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
21
frontend/src/Artist/History/ArtistHistoryTable.js
Normal file
@@ -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;
|
||||||
113
frontend/src/Artist/History/ArtistHistoryTableContent.js
Normal file
@@ -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'
|
enableColorImpairedMode && 'colorImpaired'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div>Continuing (All tracks downloaded)</div>
|
<div>Continuing (All books downloaded)</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.legendItem}>
|
<div className={styles.legendItem}>
|
||||||
@@ -70,7 +70,7 @@ class ArtistIndexFooter extends PureComponent {
|
|||||||
enableColorImpairedMode && 'colorImpaired'
|
enableColorImpairedMode && 'colorImpaired'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div>Ended (All tracks downloaded)</div>
|
<div>Ended (All books downloaded)</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.legendItem}>
|
<div className={styles.legendItem}>
|
||||||
@@ -80,7 +80,7 @@ class ArtistIndexFooter extends PureComponent {
|
|||||||
enableColorImpairedMode && 'colorImpaired'
|
enableColorImpairedMode && 'colorImpaired'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div>Missing Tracks (Artist monitored)</div>
|
<div>Missing Books (Author monitored)</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.legendItem}>
|
<div className={styles.legendItem}>
|
||||||
@@ -90,14 +90,14 @@ class ArtistIndexFooter extends PureComponent {
|
|||||||
enableColorImpairedMode && 'colorImpaired'
|
enableColorImpairedMode && 'colorImpaired'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div>Missing Tracks (Artist not monitored)</div>
|
<div>Missing Books (Author not monitored)</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.statistics}>
|
<div className={styles.statistics}>
|
||||||
<DescriptionList>
|
<DescriptionList>
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title="Artist"
|
title="Authors"
|
||||||
data={count}
|
data={count}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ class ArtistIndexFooter extends PureComponent {
|
|||||||
|
|
||||||
<DescriptionList>
|
<DescriptionList>
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title="Tracks"
|
title="Books"
|
||||||
data={tracks}
|
data={tracks}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -58,14 +58,14 @@ function createMapStateToProps() {
|
|||||||
const isRefreshingArtist = executingCommands.some((command) => {
|
const isRefreshingArtist = executingCommands.some((command) => {
|
||||||
return (
|
return (
|
||||||
command.name === commandNames.REFRESH_ARTIST &&
|
command.name === commandNames.REFRESH_ARTIST &&
|
||||||
command.body.artistId === artist.id
|
command.body.authorId === artist.id
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const isSearchingArtist = executingCommands.some((command) => {
|
const isSearchingArtist = executingCommands.some((command) => {
|
||||||
return (
|
return (
|
||||||
command.name === commandNames.ARTIST_SEARCH &&
|
command.name === commandNames.ARTIST_SEARCH &&
|
||||||
command.body.artistId === artist.id
|
command.body.authorId === artist.id
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -96,14 +96,14 @@ class ArtistIndexItemConnector extends Component {
|
|||||||
onRefreshArtistPress = () => {
|
onRefreshArtistPress = () => {
|
||||||
this.props.dispatchExecuteCommand({
|
this.props.dispatchExecuteCommand({
|
||||||
name: commandNames.REFRESH_ARTIST,
|
name: commandNames.REFRESH_ARTIST,
|
||||||
artistId: this.props.id
|
authorId: this.props.id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onSearchPress = () => {
|
onSearchPress = () => {
|
||||||
this.props.dispatchExecuteCommand({
|
this.props.dispatchExecuteCommand({
|
||||||
name: commandNames.ARTIST_SEARCH,
|
name: commandNames.ARTIST_SEARCH,
|
||||||
artistId: this.props.id
|
authorId: this.props.id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ $hoverScale: 1.05;
|
|||||||
left: 10px;
|
left: 10px;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: #216044;
|
background-color: $themeLightColor;
|
||||||
color: $white;
|
color: $white;
|
||||||
font-size: $smallFontSize;
|
font-size: $smallFontSize;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class ArtistIndexBanner extends Component {
|
|||||||
artistName,
|
artistName,
|
||||||
monitored,
|
monitored,
|
||||||
status,
|
status,
|
||||||
foreignArtistId,
|
titleSlug,
|
||||||
nextAiring,
|
nextAiring,
|
||||||
statistics,
|
statistics,
|
||||||
images,
|
images,
|
||||||
@@ -93,7 +93,7 @@ class ArtistIndexBanner extends Component {
|
|||||||
isDeleteArtistModalOpen
|
isDeleteArtistModalOpen
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const link = `/artist/${foreignArtistId}`;
|
const link = `/author/${titleSlug}`;
|
||||||
|
|
||||||
const elementStyle = {
|
const elementStyle = {
|
||||||
width: `${bannerWidth}px`,
|
width: `${bannerWidth}px`,
|
||||||
@@ -216,14 +216,14 @@ class ArtistIndexBanner extends Component {
|
|||||||
|
|
||||||
<EditArtistModalConnector
|
<EditArtistModalConnector
|
||||||
isOpen={isEditArtistModalOpen}
|
isOpen={isEditArtistModalOpen}
|
||||||
artistId={id}
|
authorId={id}
|
||||||
onModalClose={this.onEditArtistModalClose}
|
onModalClose={this.onEditArtistModalClose}
|
||||||
onDeleteArtistPress={this.onDeleteArtistPress}
|
onDeleteArtistPress={this.onDeleteArtistPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeleteArtistModal
|
<DeleteArtistModal
|
||||||
isOpen={isDeleteArtistModalOpen}
|
isOpen={isDeleteArtistModalOpen}
|
||||||
artistId={id}
|
authorId={id}
|
||||||
onModalClose={this.onDeleteArtistModalClose}
|
onModalClose={this.onDeleteArtistModalClose}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -237,7 +237,7 @@ ArtistIndexBanner.propTypes = {
|
|||||||
artistName: PropTypes.string.isRequired,
|
artistName: PropTypes.string.isRequired,
|
||||||
monitored: PropTypes.bool.isRequired,
|
monitored: PropTypes.bool.isRequired,
|
||||||
status: PropTypes.string.isRequired,
|
status: PropTypes.string.isRequired,
|
||||||
foreignArtistId: PropTypes.string.isRequired,
|
titleSlug: PropTypes.string.isRequired,
|
||||||
nextAiring: PropTypes.string,
|
nextAiring: PropTypes.string,
|
||||||
statistics: PropTypes.object.isRequired,
|
statistics: PropTypes.object.isRequired,
|
||||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ class ArtistIndexBanners extends Component {
|
|||||||
showRelativeDates={showRelativeDates}
|
showRelativeDates={showRelativeDates}
|
||||||
shortDateFormat={shortDateFormat}
|
shortDateFormat={shortDateFormat}
|
||||||
timeFormat={timeFormat}
|
timeFormat={timeFormat}
|
||||||
artistId={artist.id}
|
authorId={artist.id}
|
||||||
qualityProfileId={artist.qualityProfileId}
|
qualityProfileId={artist.qualityProfileId}
|
||||||
metadataProfileId={artist.metadataProfileId}
|
metadataProfileId={artist.metadataProfileId}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class ArtistIndexOverview extends Component {
|
|||||||
overview,
|
overview,
|
||||||
monitored,
|
monitored,
|
||||||
status,
|
status,
|
||||||
foreignArtistId,
|
titleSlug,
|
||||||
nextAiring,
|
nextAiring,
|
||||||
statistics,
|
statistics,
|
||||||
images,
|
images,
|
||||||
@@ -110,7 +110,7 @@ class ArtistIndexOverview extends Component {
|
|||||||
isDeleteArtistModalOpen
|
isDeleteArtistModalOpen
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const link = `/artist/${foreignArtistId}`;
|
const link = `/author/${titleSlug}`;
|
||||||
|
|
||||||
const elementStyle = {
|
const elementStyle = {
|
||||||
width: `${posterWidth}px`,
|
width: `${posterWidth}px`,
|
||||||
@@ -228,14 +228,14 @@ class ArtistIndexOverview extends Component {
|
|||||||
|
|
||||||
<EditArtistModalConnector
|
<EditArtistModalConnector
|
||||||
isOpen={isEditArtistModalOpen}
|
isOpen={isEditArtistModalOpen}
|
||||||
artistId={id}
|
authorId={id}
|
||||||
onModalClose={this.onEditArtistModalClose}
|
onModalClose={this.onEditArtistModalClose}
|
||||||
onDeleteArtistPress={this.onDeleteArtistPress}
|
onDeleteArtistPress={this.onDeleteArtistPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeleteArtistModal
|
<DeleteArtistModal
|
||||||
isOpen={isDeleteArtistModalOpen}
|
isOpen={isDeleteArtistModalOpen}
|
||||||
artistId={id}
|
authorId={id}
|
||||||
onModalClose={this.onDeleteArtistModalClose}
|
onModalClose={this.onDeleteArtistModalClose}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -249,7 +249,7 @@ ArtistIndexOverview.propTypes = {
|
|||||||
overview: PropTypes.string.isRequired,
|
overview: PropTypes.string.isRequired,
|
||||||
monitored: PropTypes.bool.isRequired,
|
monitored: PropTypes.bool.isRequired,
|
||||||
status: PropTypes.string.isRequired,
|
status: PropTypes.string.isRequired,
|
||||||
foreignArtistId: PropTypes.string.isRequired,
|
titleSlug: PropTypes.string.isRequired,
|
||||||
nextAiring: PropTypes.string,
|
nextAiring: PropTypes.string,
|
||||||
statistics: PropTypes.object.isRequired,
|
statistics: PropTypes.object.isRequired,
|
||||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ class ArtistIndexOverviews extends Component {
|
|||||||
longDateFormat={longDateFormat}
|
longDateFormat={longDateFormat}
|
||||||
timeFormat={timeFormat}
|
timeFormat={timeFormat}
|
||||||
isSmallScreen={isSmallScreen}
|
isSmallScreen={isSmallScreen}
|
||||||
artistId={artist.id}
|
authorId={artist.id}
|
||||||
qualityProfileId={artist.qualityProfileId}
|
qualityProfileId={artist.qualityProfileId}
|
||||||
metadataProfileId={artist.metadataProfileId}
|
metadataProfileId={artist.metadataProfileId}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ $hoverScale: 1.05;
|
|||||||
left: 10px;
|
left: 10px;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: #216044;
|
background-color: $themeLightColor;
|
||||||
color: $white;
|
color: $white;
|
||||||
font-size: $smallFontSize;
|
font-size: $smallFontSize;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class ArtistIndexPoster extends Component {
|
|||||||
id,
|
id,
|
||||||
artistName,
|
artistName,
|
||||||
monitored,
|
monitored,
|
||||||
foreignArtistId,
|
titleSlug,
|
||||||
status,
|
status,
|
||||||
nextAiring,
|
nextAiring,
|
||||||
statistics,
|
statistics,
|
||||||
@@ -107,12 +107,13 @@ class ArtistIndexPoster extends Component {
|
|||||||
isDeleteArtistModalOpen
|
isDeleteArtistModalOpen
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const link = `/artist/${foreignArtistId}`;
|
const link = `/author/${titleSlug}`;
|
||||||
|
|
||||||
const elementStyle = {
|
const elementStyle = {
|
||||||
width: `${posterWidth}px`,
|
width: `${posterWidth}px`,
|
||||||
height: `${posterHeight}px`
|
height: `${posterHeight}px`
|
||||||
};
|
};
|
||||||
|
elementStyle['object-fit'] = 'contain';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
@@ -239,14 +240,14 @@ class ArtistIndexPoster extends Component {
|
|||||||
|
|
||||||
<EditArtistModalConnector
|
<EditArtistModalConnector
|
||||||
isOpen={isEditArtistModalOpen}
|
isOpen={isEditArtistModalOpen}
|
||||||
artistId={id}
|
authorId={id}
|
||||||
onModalClose={this.onEditArtistModalClose}
|
onModalClose={this.onEditArtistModalClose}
|
||||||
onDeleteArtistPress={this.onDeleteArtistPress}
|
onDeleteArtistPress={this.onDeleteArtistPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeleteArtistModal
|
<DeleteArtistModal
|
||||||
isOpen={isDeleteArtistModalOpen}
|
isOpen={isDeleteArtistModalOpen}
|
||||||
artistId={id}
|
authorId={id}
|
||||||
onModalClose={this.onDeleteArtistModalClose}
|
onModalClose={this.onDeleteArtistModalClose}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -260,7 +261,7 @@ ArtistIndexPoster.propTypes = {
|
|||||||
artistName: PropTypes.string.isRequired,
|
artistName: PropTypes.string.isRequired,
|
||||||
monitored: PropTypes.bool.isRequired,
|
monitored: PropTypes.bool.isRequired,
|
||||||
status: PropTypes.string.isRequired,
|
status: PropTypes.string.isRequired,
|
||||||
foreignArtistId: PropTypes.string.isRequired,
|
titleSlug: PropTypes.string.isRequired,
|
||||||
nextAiring: PropTypes.string,
|
nextAiring: PropTypes.string,
|
||||||
statistics: PropTypes.object.isRequired,
|
statistics: PropTypes.object.isRequired,
|
||||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
|||||||
@@ -204,8 +204,8 @@ class ArtistIndexPosters extends Component {
|
|||||||
showQualityProfile
|
showQualityProfile
|
||||||
} = posterOptions;
|
} = posterOptions;
|
||||||
|
|
||||||
const artistIdx = rowIndex * columnCount + columnIndex;
|
const authorIdx = rowIndex * columnCount + columnIndex;
|
||||||
const artist = items[artistIdx];
|
const artist = items[authorIdx];
|
||||||
|
|
||||||
if (!artist) {
|
if (!artist) {
|
||||||
return null;
|
return null;
|
||||||
@@ -229,7 +229,7 @@ class ArtistIndexPosters extends Component {
|
|||||||
showRelativeDates={showRelativeDates}
|
showRelativeDates={showRelativeDates}
|
||||||
shortDateFormat={shortDateFormat}
|
shortDateFormat={shortDateFormat}
|
||||||
timeFormat={timeFormat}
|
timeFormat={timeFormat}
|
||||||
artistId={artist.id}
|
authorId={artist.id}
|
||||||
qualityProfileId={artist.qualityProfileId}
|
qualityProfileId={artist.qualityProfileId}
|
||||||
metadataProfileId={artist.metadataProfileId}
|
metadataProfileId={artist.metadataProfileId}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -78,14 +78,14 @@ class ArtistIndexActionsCell extends Component {
|
|||||||
|
|
||||||
<EditArtistModalConnector
|
<EditArtistModalConnector
|
||||||
isOpen={isEditArtistModalOpen}
|
isOpen={isEditArtistModalOpen}
|
||||||
artistId={id}
|
authorId={id}
|
||||||
onModalClose={this.onEditArtistModalClose}
|
onModalClose={this.onEditArtistModalClose}
|
||||||
onDeleteArtistPress={this.onDeleteArtistPress}
|
onDeleteArtistPress={this.onDeleteArtistPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeleteArtistModal
|
<DeleteArtistModal
|
||||||
isOpen={isDeleteArtistModalOpen}
|
isOpen={isDeleteArtistModalOpen}
|
||||||
artistId={id}
|
authorId={id}
|
||||||
onModalClose={this.onDeleteArtistModalClose}
|
onModalClose={this.onDeleteArtistModalClose}
|
||||||
/>
|
/>
|
||||||
</VirtualTableRowCell>
|
</VirtualTableRowCell>
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ class ArtistIndexRow extends Component {
|
|||||||
monitored,
|
monitored,
|
||||||
status,
|
status,
|
||||||
artistName,
|
artistName,
|
||||||
foreignArtistId,
|
titleSlug,
|
||||||
artistType,
|
artistType,
|
||||||
qualityProfile,
|
qualityProfile,
|
||||||
metadataProfile,
|
metadataProfile,
|
||||||
@@ -157,7 +157,7 @@ class ArtistIndexRow extends Component {
|
|||||||
showBanners ?
|
showBanners ?
|
||||||
<Link
|
<Link
|
||||||
className={styles.link}
|
className={styles.link}
|
||||||
to={`/artist/${foreignArtistId}`}
|
to={`/author/${titleSlug}`}
|
||||||
>
|
>
|
||||||
<ArtistBanner
|
<ArtistBanner
|
||||||
className={styles.bannerImage}
|
className={styles.bannerImage}
|
||||||
@@ -177,7 +177,7 @@ class ArtistIndexRow extends Component {
|
|||||||
</Link> :
|
</Link> :
|
||||||
|
|
||||||
<ArtistNameLink
|
<ArtistNameLink
|
||||||
foreignArtistId={foreignArtistId}
|
titleSlug={titleSlug}
|
||||||
artistName={artistName}
|
artistName={artistName}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@@ -228,7 +228,7 @@ class ArtistIndexRow extends Component {
|
|||||||
<AlbumTitleLink
|
<AlbumTitleLink
|
||||||
title={nextAlbum.title}
|
title={nextAlbum.title}
|
||||||
disambiguation={nextAlbum.disambiguation}
|
disambiguation={nextAlbum.disambiguation}
|
||||||
foreignAlbumId={nextAlbum.foreignAlbumId}
|
titleSlug={nextAlbum.titleSlug}
|
||||||
/>
|
/>
|
||||||
</VirtualTableRowCell>
|
</VirtualTableRowCell>
|
||||||
);
|
);
|
||||||
@@ -253,7 +253,7 @@ class ArtistIndexRow extends Component {
|
|||||||
<AlbumTitleLink
|
<AlbumTitleLink
|
||||||
title={lastAlbum.title}
|
title={lastAlbum.title}
|
||||||
disambiguation={lastAlbum.disambiguation}
|
disambiguation={lastAlbum.disambiguation}
|
||||||
foreignAlbumId={lastAlbum.foreignAlbumId}
|
titleSlug={lastAlbum.titleSlug}
|
||||||
/>
|
/>
|
||||||
</VirtualTableRowCell>
|
</VirtualTableRowCell>
|
||||||
);
|
);
|
||||||
@@ -423,14 +423,14 @@ class ArtistIndexRow extends Component {
|
|||||||
|
|
||||||
<EditArtistModalConnector
|
<EditArtistModalConnector
|
||||||
isOpen={isEditArtistModalOpen}
|
isOpen={isEditArtistModalOpen}
|
||||||
artistId={id}
|
authorId={id}
|
||||||
onModalClose={this.onEditArtistModalClose}
|
onModalClose={this.onEditArtistModalClose}
|
||||||
onDeleteArtistPress={this.onDeleteArtistPress}
|
onDeleteArtistPress={this.onDeleteArtistPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeleteArtistModal
|
<DeleteArtistModal
|
||||||
isOpen={isDeleteArtistModalOpen}
|
isOpen={isDeleteArtistModalOpen}
|
||||||
artistId={id}
|
authorId={id}
|
||||||
onModalClose={this.onDeleteArtistModalClose}
|
onModalClose={this.onDeleteArtistModalClose}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@@ -443,7 +443,7 @@ ArtistIndexRow.propTypes = {
|
|||||||
monitored: PropTypes.bool.isRequired,
|
monitored: PropTypes.bool.isRequired,
|
||||||
status: PropTypes.string.isRequired,
|
status: PropTypes.string.isRequired,
|
||||||
artistName: PropTypes.string.isRequired,
|
artistName: PropTypes.string.isRequired,
|
||||||
foreignArtistId: PropTypes.string.isRequired,
|
titleSlug: PropTypes.string.isRequired,
|
||||||
artistType: PropTypes.string,
|
artistType: PropTypes.string,
|
||||||
qualityProfile: PropTypes.object.isRequired,
|
qualityProfile: PropTypes.object.isRequired,
|
||||||
metadataProfile: PropTypes.object.isRequired,
|
metadataProfile: PropTypes.object.isRequired,
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class ArtistIndexTable extends Component {
|
|||||||
component={ArtistIndexRow}
|
component={ArtistIndexRow}
|
||||||
style={style}
|
style={style}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
artistId={artist.id}
|
authorId={artist.id}
|
||||||
qualityProfileId={artist.qualityProfileId}
|
qualityProfileId={artist.qualityProfileId}
|
||||||
metadataProfileId={artist.metadataProfileId}
|
metadataProfileId={artist.metadataProfileId}
|
||||||
showBanners={showBanners}
|
showBanners={showBanners}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ function NoArtist(props) {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.message}>
|
<div className={styles.message}>
|
||||||
All artists are hidden due to the applied filter.
|
All authors are hidden due to the applied filter.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -20,7 +20,7 @@ function NoArtist(props) {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.message}>
|
<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>
|
||||||
|
|
||||||
<div className={styles.buttonContainer}>
|
<div className={styles.buttonContainer}>
|
||||||
@@ -37,7 +37,7 @@ function NoArtist(props) {
|
|||||||
to="/add/search"
|
to="/add/search"
|
||||||
kind={kinds.PRIMARY}
|
kind={kinds.PRIMARY}
|
||||||
>
|
>
|
||||||
Add New Artist
|
Add New Author
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</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;
|
|
||||||
@@ -20,7 +20,7 @@ function Agenda(props) {
|
|||||||
return (
|
return (
|
||||||
<AgendaEventConnector
|
<AgendaEventConnector
|
||||||
key={item.id}
|
key={item.id}
|
||||||
albumId={item.id}
|
bookId={item.id}
|
||||||
showDate={showDate}
|
showDate={showDate}
|
||||||
{...item}
|
{...item}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class AgendaEvent extends Component {
|
|||||||
id,
|
id,
|
||||||
artist,
|
artist,
|
||||||
title,
|
title,
|
||||||
foreignAlbumId,
|
titleSlug,
|
||||||
releaseDate,
|
releaseDate,
|
||||||
monitored,
|
monitored,
|
||||||
statistics,
|
statistics,
|
||||||
@@ -86,7 +86,7 @@ class AgendaEvent extends Component {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.artistName}>
|
<div className={styles.artistName}>
|
||||||
<Link to={`/artist/${artist.foreignArtistId}`}>
|
<Link to={`/author/${artist.titleSlug}`}>
|
||||||
{artist.artistName}
|
{artist.artistName}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@@ -94,7 +94,7 @@ class AgendaEvent extends Component {
|
|||||||
<div className={styles.albumSeparator}> - </div>
|
<div className={styles.albumSeparator}> - </div>
|
||||||
|
|
||||||
<div className={styles.albumTitle}>
|
<div className={styles.albumTitle}>
|
||||||
<Link to={`/album/${foreignAlbumId}`}>
|
<Link to={`/book/${titleSlug}`}>
|
||||||
{title}
|
{title}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@@ -123,7 +123,7 @@ AgendaEvent.propTypes = {
|
|||||||
id: PropTypes.number.isRequired,
|
id: PropTypes.number.isRequired,
|
||||||
artist: PropTypes.object.isRequired,
|
artist: PropTypes.object.isRequired,
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
foreignAlbumId: PropTypes.string.isRequired,
|
titleSlug: PropTypes.string.isRequired,
|
||||||
albumType: PropTypes.string.isRequired,
|
albumType: PropTypes.string.isRequired,
|
||||||
releaseDate: PropTypes.string.isRequired,
|
releaseDate: PropTypes.string.isRequired,
|
||||||
monitored: PropTypes.bool.isRequired,
|
monitored: PropTypes.bool.isRequired,
|
||||||
|
|||||||