mirror of
https://github.com/lidarr/lidarr.git
synced 2025-07-15 09:33:52 -07:00
UI Updates (Cancel Import, Move Artist, Manual Import from Artist)
Ability to cancel an import lookup/search at any point. Ability to move artist path from Artist Edit or bulk move from Mass Editor. Trigger manual import for Artist path from Artist Detail page. Pulled from Sonarr
This commit is contained in:
parent
5fae202760
commit
d8c89f5bbd
79 changed files with 1075 additions and 376 deletions
|
@ -19,6 +19,12 @@
|
|||
height: 35px;
|
||||
}
|
||||
|
||||
.loadingButton {
|
||||
composes: importButton;
|
||||
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.loading {
|
||||
composes: loading from 'Components/Loading/LoadingIndicator.css';
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import _ from 'lodash';
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import Button from 'Components/Link/Button';
|
||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import CheckInput from 'Components/Form/CheckInput';
|
||||
|
@ -117,7 +118,8 @@ class ImportArtistFooter extends Component {
|
|||
isMetadataProfileIdMixed,
|
||||
showLanguageProfile,
|
||||
showMetadataProfile,
|
||||
onImportPress
|
||||
onImportPress,
|
||||
onCancelLookupPress
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
|
@ -227,10 +229,21 @@ class ImportArtistFooter extends Component {
|
|||
|
||||
{
|
||||
isLookingUpArtist &&
|
||||
<LoadingIndicator
|
||||
className={styles.loading}
|
||||
size={24}
|
||||
/>
|
||||
<Button
|
||||
className={styles.loadingButton}
|
||||
kind={kinds.WARNING}
|
||||
onPress={onCancelLookupPress}
|
||||
>
|
||||
Cancel Processing
|
||||
</Button>
|
||||
}
|
||||
|
||||
{
|
||||
isLookingUpArtist &&
|
||||
<LoadingIndicator
|
||||
className={styles.loading}
|
||||
size={24}
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -261,7 +274,8 @@ ImportArtistFooter.propTypes = {
|
|||
showLanguageProfile: PropTypes.bool.isRequired,
|
||||
showMetadataProfile: PropTypes.bool.isRequired,
|
||||
onInputChange: PropTypes.func.isRequired,
|
||||
onImportPress: PropTypes.func.isRequired
|
||||
onImportPress: PropTypes.func.isRequired,
|
||||
onCancelLookupPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default ImportArtistFooter;
|
||||
|
|
|
@ -2,6 +2,7 @@ import _ from 'lodash';
|
|||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import ImportArtistFooter from './ImportArtistFooter';
|
||||
import { cancelLookupArtist } from 'Store/Actions/importArtistActions';
|
||||
|
||||
function isMixed(items, selectedIds, defaultValue, key) {
|
||||
return _.some(items, (artist) => {
|
||||
|
@ -23,11 +24,11 @@ function createMapStateToProps() {
|
|||
albumFolder: defaultAlbumFolder
|
||||
} = addArtist.defaults;
|
||||
|
||||
const items = importArtist.items;
|
||||
|
||||
const isLookingUpArtist = _.some(importArtist.items, (artist) => {
|
||||
return !artist.isPopulated && artist.error == null;
|
||||
});
|
||||
const {
|
||||
isLookingUpArtist,
|
||||
isImporting,
|
||||
items
|
||||
} = importArtist;
|
||||
|
||||
const isMonitorMixed = isMixed(items, selectedIds, defaultMonitor, 'monitor');
|
||||
const isQualityProfileIdMixed = isMixed(items, selectedIds, defaultQualityProfileId, 'qualityProfileId');
|
||||
|
@ -37,8 +38,8 @@ function createMapStateToProps() {
|
|||
|
||||
return {
|
||||
selectedCount: selectedIds.length,
|
||||
isImporting: importArtist.isImporting,
|
||||
isLookingUpArtist,
|
||||
isImporting,
|
||||
defaultMonitor,
|
||||
defaultQualityProfileId,
|
||||
defaultLanguageProfileId,
|
||||
|
@ -54,4 +55,8 @@ function createMapStateToProps() {
|
|||
);
|
||||
}
|
||||
|
||||
export default connect(createMapStateToProps)(ImportArtistFooter);
|
||||
const mapDispatchToProps = {
|
||||
onCancelLookupPress: cancelLookupArtist
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(ImportArtistFooter);
|
||||
|
|
|
@ -10,12 +10,6 @@ class ImportArtistTable extends Component {
|
|||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this._table = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
unmappedFolders,
|
||||
|
@ -101,22 +95,11 @@ class ImportArtistTable extends Component {
|
|||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// Forces the table to re-render if the selected state
|
||||
// has changed otherwise it will be stale.
|
||||
|
||||
if (prevProps.selectedState !== selectedState && this._table) {
|
||||
this._table.forceUpdateGrid();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
setTableRef = (ref) => {
|
||||
this._table = ref;
|
||||
}
|
||||
|
||||
rowRenderer = ({ key, rowIndex, style }) => {
|
||||
const {
|
||||
rootFolderId,
|
||||
|
@ -156,6 +139,7 @@ class ImportArtistTable extends Component {
|
|||
showLanguageProfile,
|
||||
showMetadataProfile,
|
||||
scrollTop,
|
||||
selectedState,
|
||||
onSelectAllChange,
|
||||
onScroll
|
||||
} = this.props;
|
||||
|
@ -166,7 +150,6 @@ class ImportArtistTable extends Component {
|
|||
|
||||
return (
|
||||
<VirtualTable
|
||||
ref={this.setTableRef}
|
||||
items={items}
|
||||
contentBody={contentBody}
|
||||
isSmallScreen={isSmallScreen}
|
||||
|
@ -183,6 +166,7 @@ class ImportArtistTable extends Component {
|
|||
onSelectAllChange={onSelectAllChange}
|
||||
/>
|
||||
}
|
||||
selectedState={selectedState}
|
||||
onScroll={onScroll}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -66,6 +66,5 @@
|
|||
.searchInput {
|
||||
composes: text from 'Components/Form/TextInput.css';
|
||||
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import TetherComponent from 'react-tether';
|
|||
import { icons, kinds } from 'Helpers/Props';
|
||||
import Icon from 'Components/Icon';
|
||||
import SpinnerIcon from 'Components/SpinnerIcon';
|
||||
import FormInputButton from 'Components/Form/FormInputButton';
|
||||
import Link from 'Components/Link/Link';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import TextInput from 'Components/Form/TextInput';
|
||||
|
@ -99,6 +100,10 @@ class ImportArtistSelectArtist extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
onRefreshPress = () => {
|
||||
this.props.onSearchInputChange(this.state.term);
|
||||
}
|
||||
|
||||
onArtistSelect = (foreignArtistId) => {
|
||||
this.setState({ isOpen: false });
|
||||
|
||||
|
@ -116,7 +121,8 @@ class ImportArtistSelectArtist extends Component {
|
|||
isPopulated,
|
||||
error,
|
||||
items,
|
||||
queued
|
||||
queued,
|
||||
isLookingUpArtist
|
||||
} = this.props;
|
||||
|
||||
const errorMessage = error &&
|
||||
|
@ -137,7 +143,7 @@ class ImportArtistSelectArtist extends Component {
|
|||
onPress={this.onPress}
|
||||
>
|
||||
{
|
||||
queued && !isPopulated &&
|
||||
isLookingUpArtist && queued && !isPopulated &&
|
||||
<LoadingIndicator
|
||||
className={styles.loading}
|
||||
size={20}
|
||||
|
@ -206,10 +212,7 @@ class ImportArtistSelectArtist extends Component {
|
|||
<div className={styles.content}>
|
||||
<div className={styles.searchContainer}>
|
||||
<div className={styles.searchIconContainer}>
|
||||
<SpinnerIcon
|
||||
name={icons.SEARCH}
|
||||
isSpinning={isFetching}
|
||||
/>
|
||||
<Icon name={icons.SEARCH} />
|
||||
</div>
|
||||
|
||||
<TextInput
|
||||
|
@ -218,6 +221,16 @@ class ImportArtistSelectArtist extends Component {
|
|||
value={this.state.term}
|
||||
onChange={this.onSearchInputChange}
|
||||
/>
|
||||
|
||||
<FormInputButton
|
||||
kind={kinds.DEFAULT}
|
||||
spinnerIcon={icons.REFRESH}
|
||||
canSpin={true}
|
||||
isSpinning={isFetching}
|
||||
onPress={this.onRefreshPress}
|
||||
>
|
||||
<Icon name={icons.REFRESH} />
|
||||
</FormInputButton>
|
||||
</div>
|
||||
|
||||
<div className={styles.results}>
|
||||
|
@ -253,6 +266,7 @@ ImportArtistSelectArtist.propTypes = {
|
|||
error: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
queued: PropTypes.bool.isRequired,
|
||||
isLookingUpArtist: PropTypes.bool.isRequired,
|
||||
onSearchInputChange: PropTypes.func.isRequired,
|
||||
onArtistSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
|
|
@ -9,9 +9,13 @@ import ImportArtistSelectArtist from './ImportArtistSelectArtist';
|
|||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.importArtist.isLookingUpArtist,
|
||||
createImportArtistItemSelector(),
|
||||
(item) => {
|
||||
return item;
|
||||
(isLookingUpArtist, item) => {
|
||||
return {
|
||||
isLookingUpArtist,
|
||||
...item
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -29,7 +33,8 @@ class ImportArtistSelectArtistConnector extends Component {
|
|||
onSearchInputChange = (term) => {
|
||||
this.props.queueLookupArtist({
|
||||
name: this.props.id,
|
||||
term
|
||||
term,
|
||||
topOfQueue: true
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue