New: Release Profiles, Frontend updates (#580)

* New: Release Profiles - UI Updates

* New: Release Profiles - API Changes

* New: Release Profiles - Test Updates

* New: Release Profiles - Backend Updates

* New: Interactive Artist Search

* New: Change Montiored on Album Details Page

* New: Show Duration on Album Details Page

* Fixed: Manual Import not working if no albums are Missing

* Fixed: Sort search input by sortTitle

* Fixed: Queue columnLabel throwing JS error
This commit is contained in:
Qstick 2019-02-23 17:39:11 -05:00 committed by GitHub
parent f126eafd26
commit 3f064c94b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
409 changed files with 6882 additions and 3176 deletions

View file

@ -3,9 +3,10 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
import removeOldSelectedState from 'Utilities/Table/removeOldSelectedState';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
import { icons } from 'Helpers/Props';
import { align, icons } from 'Helpers/Props';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
@ -16,7 +17,9 @@ import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import RemoveQueueItemsModal from './RemoveQueueItemsModal';
import QueueOptionsConnector from './QueueOptionsConnector';
import QueueRowConnector from './QueueRowConnector';
class Queue extends Component {
@ -42,22 +45,27 @@ class Queue extends Component {
// before albums start fetching or when albums start fetching.
if (
(
this.props.isFetching &&
nextProps.isPopulated &&
hasDifferentItems(this.props.items, nextProps.items)
) ||
(!this.props.isAlbumsFetching && nextProps.isAlbumsFetching)
this.props.isFetching &&
nextProps.isPopulated &&
hasDifferentItems(this.props.items, nextProps.items) &&
nextProps.items.some((e) => e.albumId)
) {
return false;
}
if (!this.props.isAlbumsFetching && nextProps.isAlbumsFetching) {
return false;
}
return true;
}
componentDidUpdate(prevProps) {
if (hasDifferentItems(prevProps.items, this.props.items)) {
this.setState({ selectedState: {} });
this.setState((state) => {
return removeOldSelectedState(state, prevProps.items);
});
return;
}
@ -138,7 +146,7 @@ class Queue extends Component {
} = this.state;
const isRefreshing = isFetching || isAlbumsFetching || isCheckForFinishedDownloadExecuting;
const isAllPopulated = isPopulated && (isAlbumsPopulated || !items.length);
const isAllPopulated = isPopulated && (isAlbumsPopulated || !items.length || items.every((e) => !e.albumId));
const hasError = error || albumsError;
const selectedCount = this.getSelectedIds().length;
const disableSelectedActions = selectedCount === 0;
@ -172,6 +180,21 @@ class Queue extends Component {
onPress={this.onRemoveSelectedPress}
/>
</PageToolbarSection>
<PageToolbarSection
alignContent={align.RIGHT}
>
<TableOptionsModalWrapper
columns={columns}
{...otherProps}
optionsComponent={QueueOptionsConnector}
>
<PageToolbarButton
label="Options"
iconName={icons.TABLE}
/>
</TableOptionsModalWrapper>
</PageToolbarSection>
</PageToolbar>
<PageContentBodyConnector>
@ -203,6 +226,7 @@ class Queue extends Component {
allSelected={allSelected}
allUnselected={allUnselected}
{...otherProps}
optionsComponent={QueueOptionsConnector}
onSelectAllChange={this.onSelectAllChange}
>
<TableBody>

View file

@ -5,6 +5,7 @@ import { createSelector } from 'reselect';
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
import selectUniqueIds from 'Utilities/Object/selectUniqueIds';
import withCurrentPage from 'Components/withCurrentPage';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import { executeCommand } from 'Store/Actions/commandActions';
import * as queueActions from 'Store/Actions/queueActions';
@ -15,14 +16,16 @@ import Queue from './Queue';
function createMapStateToProps() {
return createSelector(
(state) => state.albums,
(state) => state.queue.options,
(state) => state.queue.paged,
createCommandExecutingSelector(commandNames.CHECK_FOR_FINISHED_DOWNLOAD),
(albums, queue, isCheckForFinishedDownloadExecuting) => {
(albums, options, queue, isCheckForFinishedDownloadExecuting) => {
return {
isAlbumsFetching: albums.isFetching,
isAlbumsPopulated: albums.isPopulated,
albumsError: albums.error,
isCheckForFinishedDownloadExecuting,
...options,
...queue
};
}
@ -42,19 +45,37 @@ class QueueConnector extends Component {
// Lifecycle
componentDidMount() {
const {
useCurrentPage,
fetchQueue,
gotoQueueFirstPage
} = this.props;
registerPagePopulator(this.repopulate);
this.props.gotoQueueFirstPage();
if (useCurrentPage) {
fetchQueue();
} else {
gotoQueueFirstPage();
}
}
componentDidUpdate(prevProps) {
if (hasDifferentItems(prevProps.items, this.props.items)) {
const albumIds = selectUniqueIds(this.props.items, 'albumId');
if (albumIds.length) {
this.props.fetchAlbums({ albumIds });
} else {
this.props.clearAlbums();
}
}
if (
this.props.includeUnknownArtistItems !==
prevProps.includeUnknownArtistItems
) {
this.repopulate();
}
}
@ -160,4 +181,6 @@ QueueConnector.propTypes = {
executeCommand: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(QueueConnector);
export default withCurrentPage(
connect(createMapStateToProps, mapDispatchToProps)(QueueConnector)
);

View file

@ -0,0 +1,77 @@
import PropTypes from 'prop-types';
import React, { Component, Fragment } from 'react';
import { inputTypes } from 'Helpers/Props';
import FormGroup from 'Components/Form/FormGroup';
import FormLabel from 'Components/Form/FormLabel';
import FormInputGroup from 'Components/Form/FormInputGroup';
class QueueOptions extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
includeUnknownArtistItems: props.includeUnknownArtistItems
};
}
componentDidUpdate(prevProps) {
const {
includeUnknownArtistItems
} = this.props;
if (includeUnknownArtistItems !== prevProps.includeUnknownArtistItems) {
this.setState({
includeUnknownArtistItems
});
}
}
//
// Listeners
onOptionChange = ({ name, value }) => {
this.setState({
[name]: value
}, () => {
this.props.onOptionChange({
[name]: value
});
});
}
//
// Render
render() {
const {
includeUnknownArtistItems
} = this.state;
return (
<Fragment>
<FormGroup>
<FormLabel>Show Unknown Artist Items</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="includeUnknownArtistItems"
value={includeUnknownArtistItems}
helpText="Show items without a artist in the queue, this could include removed artists, movies or anything else in Lidarr's category"
onChange={this.onOptionChange}
/>
</FormGroup>
</Fragment>
);
}
}
QueueOptions.propTypes = {
includeUnknownArtistItems: PropTypes.bool.isRequired,
onOptionChange: PropTypes.func.isRequired
};
export default QueueOptions;

View file

@ -0,0 +1,19 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { setQueueOption } from 'Store/Actions/queueActions';
import QueueOptions from './QueueOptions';
function createMapStateToProps() {
return createSelector(
(state) => state.queue.options,
(options) => {
return options;
}
);
}
const mapDispatchToProps = {
onOptionChange: setQueueOption
};
export default connect(createMapStateToProps, mapDispatchToProps)(QueueOptions);

View file

@ -12,6 +12,7 @@ import Icon from 'Components/Icon';
import Popover from 'Components/Tooltip/Popover';
import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
import AlbumTitleLink from 'Album/AlbumTitleLink';
import TrackLanguage from 'Album/TrackLanguage';
import TrackQuality from 'Album/TrackQuality';
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
import ArtistNameLink from 'Artist/ArtistNameLink';
@ -72,6 +73,7 @@ class QueueRow extends Component {
errorMessage,
artist,
album,
language,
quality,
protocol,
indexer,
@ -137,21 +139,14 @@ class QueueRow extends Component {
if (name === 'artist.sortName') {
return (
<TableRowCell key={name}>
<ArtistNameLink
foreignArtistId={artist.foreignArtistId}
artistName={artist.artistName}
/>
</TableRowCell>
);
}
if (name === 'artist') {
return (
<TableRowCell key={name}>
<ArtistNameLink
foreignArtistId={artist.foreignArtistId}
artistName={artist.artistName}
/>
{
artist ?
<ArtistNameLink
foreignArtistId={artist.foreignArtistId}
artistName={artist.artistName}
/> :
title
}
</TableRowCell>
);
}
@ -159,21 +154,43 @@ class QueueRow extends Component {
if (name === 'album.title') {
return (
<TableRowCell key={name}>
<AlbumTitleLink
foreignAlbumId={album.foreignAlbumId}
title={album.title}
disambiguation={album.disambiguation}
/>
{
album ?
<AlbumTitleLink
foreignAlbumId={album.foreignAlbumId}
title={album.title}
disambiguation={album.disambiguation}
/> :
'-'
}
</TableRowCell>
);
}
if (name === 'album.releaseDate') {
if (album) {
return (
<RelativeDateCellConnector
key={name}
date={album.releaseDate}
/>
);
}
return (
<RelativeDateCellConnector
key={name}
date={album.releaseDate}
/>
<TableRowCell key={name}>
-
</TableRowCell>
);
}
if (name === 'language') {
return (
<TableRowCell key={name}>
<TrackLanguage
language={language}
/>
</TableRowCell>
);
}
@ -326,8 +343,9 @@ QueueRow.propTypes = {
trackedDownloadStatus: PropTypes.string,
statusMessages: PropTypes.arrayOf(PropTypes.object),
errorMessage: PropTypes.string,
artist: PropTypes.object.isRequired,
album: PropTypes.object.isRequired,
artist: PropTypes.object,
album: PropTypes.object,
language: PropTypes.object.isRequired,
quality: PropTypes.object.isRequired,
protocol: PropTypes.string.isRequired,
indexer: PropTypes.string,

View file

@ -51,10 +51,6 @@ class QueueRowConnector extends Component {
// Render
render() {
if (!this.props.album) {
return null;
}
return (
<QueueRow
{...this.props}

View file

@ -9,12 +9,19 @@ function createMapStateToProps() {
return createSelector(
(state) => state.app,
(state) => state.queue.status,
(app, status) => {
(state) => state.queue.options.includeUnknownArtistItems,
(app, status, includeUnknownArtistItems) => {
const {
count,
unknownCount
} = status.item;
return {
isConnected: app.isConnected,
isReconnecting: app.isReconnecting,
isPopulated: status.isPopulated,
...status.item
...status.item,
count: includeUnknownArtistItems ? count : count - unknownCount
};
}
);