mirror of
https://github.com/lidarr/lidarr.git
synced 2025-07-16 10:03:51 -07:00
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:
parent
f126eafd26
commit
3f064c94b9
409 changed files with 6882 additions and 3176 deletions
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
|
|
77
frontend/src/Activity/Queue/QueueOptions.js
Normal file
77
frontend/src/Activity/Queue/QueueOptions.js
Normal 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;
|
19
frontend/src/Activity/Queue/QueueOptionsConnector.js
Normal file
19
frontend/src/Activity/Queue/QueueOptionsConnector.js
Normal 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);
|
|
@ -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,
|
||||
|
|
|
@ -51,10 +51,6 @@ class QueueRowConnector extends Component {
|
|||
// Render
|
||||
|
||||
render() {
|
||||
if (!this.props.album) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<QueueRow
|
||||
{...this.props}
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue