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
|
@ -100,8 +100,8 @@ class AddNewArtist extends Component {
|
|||
name="artistLookup"
|
||||
value={term}
|
||||
placeholder="eg. Breaking Benjamin, lidarr:854a1807-025b-42a8-ba8c-2a39717f1d25"
|
||||
onChange={this.onSearchInputChange}
|
||||
autoFocus={true}
|
||||
onChange={this.onSearchInputChange}
|
||||
/>
|
||||
|
||||
<Button
|
||||
|
|
|
@ -74,7 +74,8 @@ class AddNewArtistModalContent extends Component {
|
|||
showMetadataProfile,
|
||||
isSmallScreen,
|
||||
onModalClose,
|
||||
onInputChange
|
||||
onInputChange,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
|
@ -86,7 +87,8 @@ class AddNewArtistModalContent extends Component {
|
|||
<ModalBody>
|
||||
<div className={styles.container}>
|
||||
{
|
||||
!isSmallScreen &&
|
||||
isSmallScreen ?
|
||||
null:
|
||||
<div className={styles.poster}>
|
||||
<ArtistPoster
|
||||
className={styles.poster}
|
||||
|
@ -97,15 +99,19 @@ class AddNewArtistModalContent extends Component {
|
|||
}
|
||||
|
||||
<div className={styles.info}>
|
||||
<div className={styles.overview}>
|
||||
<TextTruncate
|
||||
truncateText="…"
|
||||
line={8}
|
||||
text={overview}
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
overview ?
|
||||
<div className={styles.overview}>
|
||||
<TextTruncate
|
||||
truncateText="…"
|
||||
line={8}
|
||||
text={overview}
|
||||
/>
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
|
||||
<Form>
|
||||
<Form {...otherProps}>
|
||||
<FormGroup>
|
||||
<FormLabel>Root Folder</FormLabel>
|
||||
|
||||
|
|
|
@ -107,8 +107,11 @@ class AddNewArtistSearchResult extends Component {
|
|||
{artistName}
|
||||
|
||||
{
|
||||
!name.contains(year) && !!year &&
|
||||
<span className={styles.year}>({year})</span>
|
||||
!name.contains(year) && year ?
|
||||
<span className={styles.year}>
|
||||
({year})
|
||||
</span> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -117,13 +120,14 @@ class AddNewArtistSearchResult extends Component {
|
|||
}
|
||||
|
||||
{
|
||||
isExistingArtist &&
|
||||
<Icon
|
||||
className={styles.alreadyExistsIcon}
|
||||
name={icons.CHECK_CIRCLE}
|
||||
size={36}
|
||||
title="Already in your library"
|
||||
/>
|
||||
isExistingArtist ?
|
||||
<Icon
|
||||
className={styles.alreadyExistsIcon}
|
||||
name={icons.CHECK_CIRCLE}
|
||||
size={36}
|
||||
title="Already in your library"
|
||||
/> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
|
||||
|
@ -136,20 +140,22 @@ class AddNewArtistSearchResult extends Component {
|
|||
</Label>
|
||||
|
||||
{
|
||||
!!artistType &&
|
||||
artistType ?
|
||||
<Label size={sizes.LARGE}>
|
||||
{artistType}
|
||||
</Label>
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
status === 'ended' &&
|
||||
status === 'ended' ?
|
||||
<Label
|
||||
kind={kinds.DANGER}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
Ended
|
||||
</Label>
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -116,9 +116,11 @@ class ImportArtistFooter extends Component {
|
|||
isQualityProfileIdMixed,
|
||||
isLanguageProfileIdMixed,
|
||||
isMetadataProfileIdMixed,
|
||||
hasUnsearchedItems,
|
||||
showLanguageProfile,
|
||||
showMetadataProfile,
|
||||
onImportPress,
|
||||
onLookupPress,
|
||||
onCancelLookupPress
|
||||
} = this.props;
|
||||
|
||||
|
@ -238,6 +240,17 @@ class ImportArtistFooter extends Component {
|
|||
</Button>
|
||||
}
|
||||
|
||||
{
|
||||
hasUnsearchedItems &&
|
||||
<Button
|
||||
className={styles.loadingButton}
|
||||
kind={kinds.SUCCESS}
|
||||
onPress={onLookupPress}
|
||||
>
|
||||
Start Processing
|
||||
</Button>
|
||||
}
|
||||
|
||||
{
|
||||
isLookingUpArtist &&
|
||||
<LoadingIndicator
|
||||
|
@ -271,10 +284,12 @@ ImportArtistFooter.propTypes = {
|
|||
isLanguageProfileIdMixed: PropTypes.bool.isRequired,
|
||||
isMetadataProfileIdMixed: PropTypes.bool.isRequired,
|
||||
isAlbumFolderMixed: PropTypes.bool.isRequired,
|
||||
hasUnsearchedItems: PropTypes.bool.isRequired,
|
||||
showLanguageProfile: PropTypes.bool.isRequired,
|
||||
showMetadataProfile: PropTypes.bool.isRequired,
|
||||
onInputChange: PropTypes.func.isRequired,
|
||||
onImportPress: PropTypes.func.isRequired,
|
||||
onLookupPress: PropTypes.func.isRequired,
|
||||
onCancelLookupPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import _ from 'lodash';
|
|||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import ImportArtistFooter from './ImportArtistFooter';
|
||||
import { cancelLookupArtist } from 'Store/Actions/importArtistActions';
|
||||
import { lookupUnsearchedArtist, cancelLookupArtist } from 'Store/Actions/importArtistActions';
|
||||
|
||||
function isMixed(items, selectedIds, defaultValue, key) {
|
||||
return _.some(items, (artist) => {
|
||||
|
@ -35,6 +35,7 @@ function createMapStateToProps() {
|
|||
const isLanguageProfileIdMixed = isMixed(items, selectedIds, defaultLanguageProfileId, 'languageProfileId');
|
||||
const isMetadataProfileIdMixed = isMixed(items, selectedIds, defaultMetadataProfileId, 'metadataProfileId');
|
||||
const isAlbumFolderMixed = isMixed(items, selectedIds, defaultAlbumFolder, 'albumFolder');
|
||||
const hasUnsearchedItems = !isLookingUpArtist && items.some((item) => !item.isPopulated);
|
||||
|
||||
return {
|
||||
selectedCount: selectedIds.length,
|
||||
|
@ -49,13 +50,15 @@ function createMapStateToProps() {
|
|||
isQualityProfileIdMixed,
|
||||
isLanguageProfileIdMixed,
|
||||
isMetadataProfileIdMixed,
|
||||
isAlbumFolderMixed
|
||||
isAlbumFolderMixed,
|
||||
hasUnsearchedItems
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
onLookupPress: lookupUnsearchedArtist,
|
||||
onCancelLookupPress: cancelLookupArtist
|
||||
};
|
||||
|
||||
|
|
|
@ -110,7 +110,6 @@ ImportArtistRow.propTypes = {
|
|||
selectedArtist: PropTypes.object,
|
||||
isExistingArtist: PropTypes.bool.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
queued: PropTypes.bool.isRequired,
|
||||
showLanguageProfile: PropTypes.bool.isRequired,
|
||||
showMetadataProfile: PropTypes.bool.isRequired,
|
||||
isSelected: PropTypes.bool,
|
||||
|
|
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { queueLookupArtist, setImportArtistValue } from 'Store/Actions/importArtistActions';
|
||||
import { setImportArtistValue } from 'Store/Actions/importArtistActions';
|
||||
import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
|
||||
import ImportArtistRow from './ImportArtistRow';
|
||||
|
||||
|
@ -34,7 +34,6 @@ function createMapStateToProps() {
|
|||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
queueLookupArtist,
|
||||
setImportArtistValue
|
||||
};
|
||||
|
||||
|
@ -82,7 +81,6 @@ ImportArtistRowConnector.propTypes = {
|
|||
monitor: PropTypes.string,
|
||||
albumFolder: PropTypes.bool,
|
||||
items: PropTypes.arrayOf(PropTypes.object),
|
||||
queueLookupArtist: PropTypes.func.isRequired,
|
||||
setImportArtistValue: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
.artistNameContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 1 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.artistName {
|
||||
margin-right: 5px;
|
||||
@add-mixin truncate;
|
||||
}
|
||||
|
||||
.disambiguation {
|
||||
|
@ -12,11 +14,6 @@
|
|||
color: $disabledColor;
|
||||
}
|
||||
|
||||
.year {
|
||||
margin-left: 5px;
|
||||
color: $disabledColor;
|
||||
}
|
||||
|
||||
.existing {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ function ImportArtistName(props) {
|
|||
const {
|
||||
artistName,
|
||||
disambiguation,
|
||||
// year,
|
||||
isExistingArtist
|
||||
} = props;
|
||||
|
||||
|
@ -36,7 +35,6 @@ function ImportArtistName(props) {
|
|||
ImportArtistName.propTypes = {
|
||||
artistName: PropTypes.string.isRequired,
|
||||
disambiguation: PropTypes.string,
|
||||
// year: PropTypes.number.isRequired,
|
||||
isExistingArtist: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
|
|
|
@ -30,8 +30,9 @@
|
|||
}
|
||||
|
||||
.dropdownArrowContainer {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
flex: 1 0 auto;
|
||||
margin-left: 5px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.contentContainer {
|
||||
|
@ -68,3 +69,13 @@
|
|||
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.results {
|
||||
@add-mixin scrollbar;
|
||||
@add-mixin scrollbarTrack;
|
||||
@add-mixin scrollbarThumb;
|
||||
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
max-height: 165px;
|
||||
}
|
||||
|
|
|
@ -120,7 +120,7 @@ class ImportArtistSelectArtist extends Component {
|
|||
isPopulated,
|
||||
error,
|
||||
items,
|
||||
queued,
|
||||
isQueued,
|
||||
isLookingUpArtist
|
||||
} = this.props;
|
||||
|
||||
|
@ -142,7 +142,7 @@ class ImportArtistSelectArtist extends Component {
|
|||
onPress={this.onPress}
|
||||
>
|
||||
{
|
||||
isLookingUpArtist && queued && !isPopulated &&
|
||||
isLookingUpArtist && isQueued && !isPopulated &&
|
||||
<LoadingIndicator
|
||||
className={styles.loading}
|
||||
size={20}
|
||||
|
@ -170,7 +170,7 @@ class ImportArtistSelectArtist extends Component {
|
|||
|
||||
{
|
||||
isPopulated && !selectedArtist &&
|
||||
<div>
|
||||
<div className={styles.noMatches}>
|
||||
<Icon
|
||||
className={styles.warningIcon}
|
||||
name={icons.WARNING}
|
||||
|
@ -265,7 +265,7 @@ ImportArtistSelectArtist.propTypes = {
|
|||
isPopulated: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
queued: PropTypes.bool.isRequired,
|
||||
isQueued: PropTypes.bool.isRequired,
|
||||
isLookingUpArtist: PropTypes.bool.isRequired,
|
||||
onSearchInputChange: PropTypes.func.isRequired,
|
||||
onArtistSelect: PropTypes.func.isRequired
|
||||
|
@ -275,7 +275,7 @@ ImportArtistSelectArtist.defaultProps = {
|
|||
isFetching: true,
|
||||
isPopulated: false,
|
||||
items: [],
|
||||
queued: true
|
||||
isQueued: true
|
||||
};
|
||||
|
||||
export default ImportArtistSelectArtist;
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
.link {
|
||||
composes: link from 'Components/Link/Link.css';
|
||||
|
||||
display: block;
|
||||
}
|
||||
|
||||
.freeSpace,
|
||||
.unmappedFolders {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 45px;
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import Link from 'Components/Link/Link';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import styles from './ImportArtistRootFolderRow.css';
|
||||
|
||||
function ImportArtistRootFolderRow(props) {
|
||||
const {
|
||||
id,
|
||||
path,
|
||||
freeSpace,
|
||||
unmappedFolders,
|
||||
onDeletePress
|
||||
} = props;
|
||||
|
||||
const unmappedFoldersCount = unmappedFolders.length || '-';
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<TableRowCell>
|
||||
<Link
|
||||
className={styles.link}
|
||||
to={`/add/import/${id}`}
|
||||
>
|
||||
{path}
|
||||
</Link>
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.freeSpace}>
|
||||
{formatBytes(freeSpace) || '-'}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.unmappedFolders}>
|
||||
{unmappedFoldersCount}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.actions}>
|
||||
<IconButton
|
||||
title="Remove root folder"
|
||||
name={icons.REMOVE}
|
||||
onPress={onDeletePress}
|
||||
/>
|
||||
</TableRowCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
ImportArtistRootFolderRow.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
freeSpace: PropTypes.number.isRequired,
|
||||
unmappedFolders: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onDeletePress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
ImportArtistRootFolderRow.defaultProps = {
|
||||
freeSpace: 0,
|
||||
unmappedFolders: []
|
||||
};
|
||||
|
||||
export default ImportArtistRootFolderRow;
|
|
@ -1,48 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { deleteRootFolder } from 'Store/Actions/rootFolderActions';
|
||||
import ImportArtistRootFolderRow from './ImportArtistRootFolderRow';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
() => {
|
||||
return {
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
deleteRootFolder
|
||||
};
|
||||
|
||||
class ImportArtistRootFolderRowConnector extends Component {
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onDeletePress = () => {
|
||||
this.props.deleteRootFolder({ id: this.props.id });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ImportArtistRootFolderRow
|
||||
{...this.props}
|
||||
onDeletePress={this.onDeletePress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ImportArtistRootFolderRowConnector.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
deleteRootFolder: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(ImportArtistRootFolderRowConnector);
|
|
@ -8,33 +8,9 @@ import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
|||
import FileBrowserModal from 'Components/FileBrowser/FileBrowserModal';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import ImportArtistRootFolderRowConnector from './ImportArtistRootFolderRowConnector';
|
||||
import RootFolders from 'RootFolder/RootFolders';
|
||||
import styles from './ImportArtistSelectFolder.css';
|
||||
|
||||
const rootFolderColumns = [
|
||||
{
|
||||
name: 'path',
|
||||
label: 'Path',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'freeSpace',
|
||||
label: 'Free Space',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'unmappedFolders',
|
||||
label: 'Unmapped Folders',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'actions',
|
||||
isVisible: true
|
||||
}
|
||||
];
|
||||
|
||||
class ImportArtistSelectFolder extends Component {
|
||||
|
||||
//
|
||||
|
@ -107,26 +83,13 @@ class ImportArtistSelectFolder extends Component {
|
|||
{
|
||||
items.length > 0 ?
|
||||
<div className={styles.recentFolders}>
|
||||
<FieldSet legend="Recent Folders">
|
||||
<Table
|
||||
columns={rootFolderColumns}
|
||||
>
|
||||
<TableBody>
|
||||
{
|
||||
items.map((rootFolder) => {
|
||||
return (
|
||||
<ImportArtistRootFolderRowConnector
|
||||
key={rootFolder.id}
|
||||
id={rootFolder.id}
|
||||
path={rootFolder.path}
|
||||
freeSpace={rootFolder.freeSpace}
|
||||
unmappedFolders={rootFolder.unmappedFolders}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<FieldSet legend="Root Folders">
|
||||
<RootFolders
|
||||
isFetching={isFetching}
|
||||
isPopulated={isPopulated}
|
||||
error={error}
|
||||
items={items}
|
||||
/>
|
||||
</FieldSet>
|
||||
|
||||
<Button
|
||||
|
@ -178,8 +141,7 @@ ImportArtistSelectFolder.propTypes = {
|
|||
isPopulated: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onNewRootFolderSelect: PropTypes.func.isRequired,
|
||||
onDeleteRootFolderPress: PropTypes.func.isRequired
|
||||
onNewRootFolderSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default ImportArtistSelectFolder;
|
||||
|
|
|
@ -5,7 +5,7 @@ import { connect } from 'react-redux';
|
|||
import { createSelector } from 'reselect';
|
||||
import { push } from 'react-router-redux';
|
||||
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
|
||||
import { fetchRootFolders, addRootFolder, deleteRootFolder } from 'Store/Actions/rootFolderActions';
|
||||
import { fetchRootFolders, addRootFolder } from 'Store/Actions/rootFolderActions';
|
||||
import ImportArtistSelectFolder from './ImportArtistSelectFolder';
|
||||
|
||||
function createMapStateToProps() {
|
||||
|
@ -24,7 +24,6 @@ function createMapStateToProps() {
|
|||
const mapDispatchToProps = {
|
||||
fetchRootFolders,
|
||||
addRootFolder,
|
||||
deleteRootFolder,
|
||||
push
|
||||
};
|
||||
|
||||
|
@ -60,10 +59,6 @@ class ImportArtistSelectFolderConnector extends Component {
|
|||
this.props.addRootFolder({ path });
|
||||
}
|
||||
|
||||
onDeleteRootFolderPress = (id) => {
|
||||
this.props.deleteRootFolder({ id });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
|
@ -72,7 +67,6 @@ class ImportArtistSelectFolderConnector extends Component {
|
|||
<ImportArtistSelectFolder
|
||||
{...this.props}
|
||||
onNewRootFolderSelect={this.onNewRootFolderSelect}
|
||||
onDeleteRootFolderPress={this.onDeleteRootFolderPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -84,7 +78,6 @@ ImportArtistSelectFolderConnector.propTypes = {
|
|||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
fetchRootFolders: PropTypes.func.isRequired,
|
||||
addRootFolder: PropTypes.func.isRequired,
|
||||
deleteRootFolder: PropTypes.func.isRequired,
|
||||
push: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue