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

@ -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

View file

@ -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>

View file

@ -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>

View file

@ -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
};

View file

@ -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
};

View file

@ -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,

View file

@ -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
};

View file

@ -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;
}

View file

@ -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
};

View file

@ -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;
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

View file

@ -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);

View file

@ -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;

View file

@ -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
};