New: Optional Search Button on Artist Index views

This commit is contained in:
Qstick 2018-11-10 23:08:31 -05:00
parent 88c58e2529
commit 0c1f9e6c8d
18 changed files with 310 additions and 24 deletions

View File

@ -13,14 +13,42 @@ import createMetadataProfileSelector from 'Store/Selectors/createMetadataProfile
import { executeCommand } from 'Store/Actions/commandActions';
import * as commandNames from 'Commands/commandNames';
function selectShowSearchAction() {
return createSelector(
(state) => state.artistIndex,
(artistIndex) => {
const view = artistIndex.view;
switch (view) {
case 'posters':
return artistIndex.posterOptions.showSearchAction;
case 'banners':
return artistIndex.bannerOptions.showSearchAction;
case 'overview':
return artistIndex.overviewOptions.showSearchAction;
default:
return artistIndex.tableOptions.showSearchAction;
}
}
);
}
function createMapStateToProps() {
return createSelector(
createArtistSelector(),
createQualityProfileSelector(),
createLanguageProfileSelector(),
createMetadataProfileSelector(),
selectShowSearchAction(),
createCommandsSelector(),
(artist, qualityProfile, languageProfile, metadataProfile, commands) => {
(
artist,
qualityProfile,
languageProfile,
metadataProfile,
showSearchAction,
commands
) => {
const isRefreshingArtist = commands.some((command) => {
return (
command.name === commandNames.REFRESH_ARTIST &&
@ -29,6 +57,14 @@ function createMapStateToProps() {
);
});
const isSearchingArtist = commands.some((command) => {
return (
command.name === commandNames.ARTIST_SEARCH &&
command.body.artistId === artist.id &&
isCommandExecuting(command)
);
});
const latestAlbum = _.maxBy(artist.albums, (album) => album.releaseDate);
return {
@ -37,7 +73,9 @@ function createMapStateToProps() {
languageProfile,
metadataProfile,
latestAlbum,
isRefreshingArtist
showSearchAction,
isRefreshingArtist,
isSearchingArtist
};
}
);
@ -59,6 +97,13 @@ class ArtistIndexItemConnector extends Component {
});
}
onSearchPress = () => {
this.props.executeCommand({
name: commandNames.ARTIST_SEARCH,
artistId: this.props.id
});
}
//
// Render
@ -72,6 +117,7 @@ class ArtistIndexItemConnector extends Component {
<ItemComponent
{...otherProps}
onRefreshArtistPress={this.onRefreshArtistPress}
onSearchPress={this.onSearchPress}
/>
);
}

View File

@ -61,6 +61,7 @@ $hoverScale: 1.05;
position: absolute;
bottom: 10px;
left: 10px;
z-index: 3;
border-radius: 4px;
background-color: #216044;
color: $white;

View File

@ -69,12 +69,15 @@ class ArtistIndexBanner extends Component {
showTitle,
showMonitored,
showQualityProfile,
showSearchAction,
qualityProfile,
showRelativeDates,
shortDateFormat,
timeFormat,
isRefreshingArtist,
isSearchingArtist,
onRefreshArtistPress,
onSearchPress,
...otherProps
} = this.props;
@ -111,6 +114,17 @@ class ArtistIndexBanner extends Component {
onPress={onRefreshArtistPress}
/>
{
showSearchAction &&
<SpinnerIconButton
className={styles.action}
name={icons.SEARCH}
title="Search for monitored albums"
isSpinning={isSearchingArtist}
onPress={onSearchPress}
/>
}
<IconButton
className={styles.action}
name={icons.EDIT}
@ -236,11 +250,14 @@ ArtistIndexBanner.propTypes = {
showMonitored: PropTypes.bool.isRequired,
showQualityProfile: PropTypes.bool.isRequired,
qualityProfile: PropTypes.object.isRequired,
showSearchAction: PropTypes.bool.isRequired,
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired,
isRefreshingArtist: PropTypes.bool.isRequired,
onRefreshArtistPress: PropTypes.func.isRequired
isSearchingArtist: PropTypes.bool.isRequired,
onRefreshArtistPress: PropTypes.func.isRequired,
onSearchPress: PropTypes.func.isRequired
};
ArtistIndexBanner.defaultProps = {

View File

@ -31,7 +31,8 @@ class ArtistIndexBannerOptionsModalContent extends Component {
size: props.size,
showTitle: props.showTitle,
showMonitored: props.showMonitored,
showQualityProfile: props.showQualityProfile
showQualityProfile: props.showQualityProfile,
showSearchAction: props.showSearchAction
};
}
@ -41,7 +42,8 @@ class ArtistIndexBannerOptionsModalContent extends Component {
size,
showTitle,
showMonitored,
showQualityProfile
showQualityProfile,
showSearchAction
} = this.props;
const state = {};
@ -66,6 +68,10 @@ class ArtistIndexBannerOptionsModalContent extends Component {
state.showQualityProfile = showQualityProfile;
}
if (showSearchAction !== prevProps.showSearchAction) {
state.showSearchAction = showSearchAction;
}
if (!_.isEmpty(state)) {
this.setState(state);
}
@ -95,7 +101,8 @@ class ArtistIndexBannerOptionsModalContent extends Component {
size,
showTitle,
showMonitored,
showQualityProfile
showQualityProfile,
showSearchAction
} = this.state;
return (
@ -165,6 +172,18 @@ class ArtistIndexBannerOptionsModalContent extends Component {
onChange={this.onChangeBannerOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>Show Search</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showSearchAction"
value={showSearchAction}
helpText="Show search button on hover"
onChange={this.onChangeBannerOption}
/>
</FormGroup>
</Form>
</ModalBody>
@ -185,6 +204,7 @@ ArtistIndexBannerOptionsModalContent.propTypes = {
showTitle: PropTypes.bool.isRequired,
showQualityProfile: PropTypes.bool.isRequired,
detailedProgressBar: PropTypes.bool.isRequired,
showSearchAction: PropTypes.bool.isRequired,
onChangeBannerOption: PropTypes.func.isRequired,
showMonitored: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired

View File

@ -84,6 +84,7 @@ class ArtistIndexOverview extends Component {
posterHeight,
qualityProfile,
overviewOptions,
showSearchAction,
showRelativeDates,
shortDateFormat,
longDateFormat,
@ -91,7 +92,9 @@ class ArtistIndexOverview extends Component {
rowHeight,
isSmallScreen,
isRefreshingArtist,
isSearchingArtist,
onRefreshArtistPress,
onSearchPress,
...otherProps
} = this.props;
@ -175,6 +178,17 @@ class ArtistIndexOverview extends Component {
onPress={onRefreshArtistPress}
/>
{
showSearchAction &&
<SpinnerIconButton
className={styles.action}
name={icons.SEARCH}
title="Search for monitored albums"
isSpinning={isSearchingArtist}
onPress={onSearchPress}
/>
}
<IconButton
name={icons.EDIT}
title="Edit Artist"
@ -246,12 +260,14 @@ ArtistIndexOverview.propTypes = {
rowHeight: PropTypes.number.isRequired,
qualityProfile: PropTypes.object.isRequired,
overviewOptions: PropTypes.object.isRequired,
showSearchAction: PropTypes.bool.isRequired,
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,
longDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired,
isSmallScreen: PropTypes.bool.isRequired,
isRefreshingArtist: PropTypes.bool.isRequired,
isSearchingArtist: PropTypes.bool.isRequired,
onRefreshArtistPress: PropTypes.func.isRequired
};

View File

@ -35,7 +35,8 @@ class ArtistIndexOverviewOptionsModalContent extends Component {
showAdded: props.showAdded,
showAlbumCount: props.showAlbumCount,
showPath: props.showPath,
showSizeOnDisk: props.showSizeOnDisk
showSizeOnDisk: props.showSizeOnDisk,
showSearchAction: props.showSearchAction
};
}
@ -49,7 +50,8 @@ class ArtistIndexOverviewOptionsModalContent extends Component {
showAdded,
showAlbumCount,
showPath,
showSizeOnDisk
showSizeOnDisk,
showSearchAction
} = this.props;
const state = {};
@ -90,6 +92,10 @@ class ArtistIndexOverviewOptionsModalContent extends Component {
state.showSizeOnDisk = showSizeOnDisk;
}
if (showSearchAction !== prevProps.showSearchAction) {
state.showSearchAction = showSearchAction;
}
if (!_.isEmpty(state)) {
this.setState(state);
}
@ -123,7 +129,8 @@ class ArtistIndexOverviewOptionsModalContent extends Component {
showAdded,
showAlbumCount,
showPath,
showSizeOnDisk
showSizeOnDisk,
showSearchAction
} = this.state;
return (
@ -235,6 +242,18 @@ class ArtistIndexOverviewOptionsModalContent extends Component {
onChange={this.onChangeOverviewOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>Show Search</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showSearchAction"
value={showSearchAction}
helpText="Show search button"
onChange={this.onChangeOverviewOption}
/>
</FormGroup>
</Form>
</ModalBody>
@ -252,6 +271,7 @@ class ArtistIndexOverviewOptionsModalContent extends Component {
ArtistIndexOverviewOptionsModalContent.propTypes = {
size: PropTypes.string.isRequired,
detailedProgressBar: PropTypes.bool.isRequired,
showMonitored: PropTypes.bool.isRequired,
showQualityProfile: PropTypes.bool.isRequired,
showLastAlbum: PropTypes.bool.isRequired,
@ -259,7 +279,7 @@ ArtistIndexOverviewOptionsModalContent.propTypes = {
showAlbumCount: PropTypes.bool.isRequired,
showPath: PropTypes.bool.isRequired,
showSizeOnDisk: PropTypes.bool.isRequired,
detailedProgressBar: PropTypes.bool.isRequired,
showSearchAction: PropTypes.bool.isRequired,
onChangeOverviewOption: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@ -61,6 +61,7 @@ $hoverScale: 1.05;
position: absolute;
bottom: 10px;
left: 10px;
z-index: 3;
border-radius: 4px;
background-color: #216044;
color: $white;

View File

@ -70,11 +70,14 @@ class ArtistIndexPoster extends Component {
showMonitored,
showQualityProfile,
qualityProfile,
showSearchAction,
showRelativeDates,
shortDateFormat,
timeFormat,
isRefreshingArtist,
isSearchingArtist,
onRefreshArtistPress,
onSearchPress,
...otherProps
} = this.props;
@ -111,6 +114,17 @@ class ArtistIndexPoster extends Component {
onPress={onRefreshArtistPress}
/>
{
showSearchAction &&
<SpinnerIconButton
className={styles.action}
name={icons.SEARCH}
title="Search for monitored albums"
isSpinning={isSearchingArtist}
onPress={onSearchPress}
/>
}
<IconButton
className={styles.action}
name={icons.EDIT}
@ -235,11 +249,14 @@ ArtistIndexPoster.propTypes = {
showMonitored: PropTypes.bool.isRequired,
showQualityProfile: PropTypes.bool.isRequired,
qualityProfile: PropTypes.object.isRequired,
showSearchAction: PropTypes.bool.isRequired,
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired,
isRefreshingArtist: PropTypes.bool.isRequired,
onRefreshArtistPress: PropTypes.func.isRequired
isSearchingArtist: PropTypes.bool.isRequired,
onRefreshArtistPress: PropTypes.func.isRequired,
onSearchPress: PropTypes.func.isRequired
};
ArtistIndexPoster.defaultProps = {

View File

@ -31,7 +31,8 @@ class ArtistIndexPosterOptionsModalContent extends Component {
size: props.size,
showTitle: props.showTitle,
showMonitored: props.showMonitored,
showQualityProfile: props.showQualityProfile
showQualityProfile: props.showQualityProfile,
showSearchAction: props.showSearchAction
};
}
@ -41,7 +42,8 @@ class ArtistIndexPosterOptionsModalContent extends Component {
size,
showTitle,
showMonitored,
showQualityProfile
showQualityProfile,
showSearchAction
} = this.props;
const state = {};
@ -66,6 +68,10 @@ class ArtistIndexPosterOptionsModalContent extends Component {
state.showQualityProfile = showQualityProfile;
}
if (showSearchAction !== prevProps.showSearchAction) {
state.showSearchAction = showSearchAction;
}
if (!_.isEmpty(state)) {
this.setState(state);
}
@ -95,7 +101,8 @@ class ArtistIndexPosterOptionsModalContent extends Component {
size,
showTitle,
showMonitored,
showQualityProfile
showQualityProfile,
showSearchAction
} = this.state;
return (
@ -165,6 +172,18 @@ class ArtistIndexPosterOptionsModalContent extends Component {
onChange={this.onChangePosterOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>Show Search</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showSearchAction"
value={showSearchAction}
helpText="Show search button on hover"
onChange={this.onChangePosterOption}
/>
</FormGroup>
</Form>
</ModalBody>
@ -186,6 +205,7 @@ ArtistIndexPosterOptionsModalContent.propTypes = {
showMonitored: PropTypes.bool.isRequired,
showQualityProfile: PropTypes.bool.isRequired,
detailedProgressBar: PropTypes.bool.isRequired,
showSearchAction: PropTypes.bool.isRequired,
onChangePosterOption: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@ -80,5 +80,5 @@
.actions {
composes: headerCell from 'Components/Table/VirtualTableHeaderCell.css';
flex: 0 0 70px;
flex: 0 1 90px;
}

View File

@ -5,6 +5,7 @@ import IconButton from 'Components/Link/IconButton';
import VirtualTableHeader from 'Components/Table/VirtualTableHeader';
import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell';
import TableOptionsModal from 'Components/Table/TableOptions/TableOptionsModal';
import ArtistIndexTableOptionsConnector from './ArtistIndexTableOptionsConnector';
import styles from './ArtistIndexHeader.css';
class ArtistIndexHeader extends Component {
@ -36,6 +37,7 @@ class ArtistIndexHeader extends Component {
render() {
const {
showSearchAction,
columns,
onTableOptionChange,
...otherProps
@ -90,6 +92,7 @@ class ArtistIndexHeader extends Component {
<TableOptionsModal
isOpen={this.state.isTableOptionsModalOpen}
columns={columns}
optionsComponent={ArtistIndexTableOptionsConnector}
onTableOptionChange={onTableOptionChange}
onModalClose={this.onTableOptionsModalClose}
/>

View File

@ -82,7 +82,7 @@
.actions {
composes: cell from 'Components/Table/Cells/VirtualTableRowCell.css';
flex: 0 0 70px;
flex: 0 1 90px;
}
.checkInput {

View File

@ -80,9 +80,12 @@ class ArtistIndexRow extends Component {
ratings,
path,
tags,
showSearchAction,
columns,
isRefreshingArtist,
onRefreshArtistPress
isSearchingArtist,
onRefreshArtistPress,
onSearchPress
} = this.props;
const {
@ -360,6 +363,17 @@ class ArtistIndexRow extends Component {
onPress={onRefreshArtistPress}
/>
{
showSearchAction &&
<SpinnerIconButton
className={styles.action}
name={icons.SEARCH}
title="Search for monitored albums"
isSpinning={isSearchingArtist}
onPress={onSearchPress}
/>
}
<IconButton
name={icons.EDIT}
title="Edit Artist"
@ -410,9 +424,12 @@ ArtistIndexRow.propTypes = {
genres: PropTypes.arrayOf(PropTypes.string).isRequired,
ratings: PropTypes.object.isRequired,
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
showSearchAction: PropTypes.bool.isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
isRefreshingArtist: PropTypes.bool.isRequired,
onRefreshArtistPress: PropTypes.func.isRequired
isSearchingArtist: PropTypes.bool.isRequired,
onRefreshArtistPress: PropTypes.func.isRequired,
onSearchPress: PropTypes.func.isRequired
};
ArtistIndexRow.defaultProps = {

View File

@ -0,0 +1,76 @@
import PropTypes from 'prop-types';
import React, { Component } 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 ArtistIndexTableOptions extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
showSearchAction: props.showSearchAction
};
}
componentDidUpdate(prevProps) {
const { showSearchAction } = this.props;
if (showSearchAction !== prevProps.showSearchAction) {
this.setState({
showSearchAction
});
}
}
//
// Listeners
onTableOptionChange = ({ name, value }) => {
this.setState({
[name]: value
}, () => {
this.props.onTableOptionChange({
tableOptions: {
...this.state,
[name]: value
}
});
});
}
//
// Render
render() {
const {
showSearchAction
} = this.state;
return (
<FormGroup>
<FormLabel>Show Search</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showSearchAction"
value={showSearchAction}
helpText="Show search button"
onChange={this.onTableOptionChange}
/>
</FormGroup>
);
}
}
ArtistIndexTableOptions.propTypes = {
showSearchAction: PropTypes.bool.isRequired,
onTableOptionChange: PropTypes.func.isRequired
};
export default ArtistIndexTableOptions;

View File

@ -0,0 +1,14 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import ArtistIndexTableOptions from './ArtistIndexTableOptions';
function createMapStateToProps() {
return createSelector(
(state) => state.artistIndex.tableOptions,
(tableOptions) => {
return tableOptions;
}
);
}
export default connect(createMapStateToProps)(ArtistIndexTableOptions);

View File

@ -109,6 +109,8 @@ class TableOptionsModal extends Component {
isOpen,
columns,
canModifyColumns,
optionsComponent: OptionsComponent,
onTableOptionChange,
onModalClose
} = this.props;
@ -152,6 +154,13 @@ class TableOptionsModal extends Component {
</FormGroup>
}
{
!!OptionsComponent &&
<OptionsComponent
onTableOptionChange={onTableOptionChange}
/>
}
{
canModifyColumns &&
<FormGroup>
@ -231,6 +240,7 @@ TableOptionsModal.propTypes = {
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
pageSize: PropTypes.number,
canModifyColumns: PropTypes.bool.isRequired,
optionsComponent: PropTypes.func.isRequired,
onTableOptionChange: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@ -4,7 +4,8 @@ import updateSectionState from 'Utilities/State/updateSectionState';
const whitelistedProperties = [
'pageSize',
'columns'
'columns',
'tableOptions'
];
function createSetTableOptionReducer(section) {

View File

@ -1,4 +1,3 @@
import moment from 'moment';
import { createAction } from 'redux-actions';
import sortByName from 'Utilities/Array/sortByName';
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
@ -28,7 +27,8 @@ export const defaultState = {
size: 'large',
showTitle: true,
showMonitored: true,
showQualityProfile: true
showQualityProfile: true,
showSearchAction: false
},
bannerOptions: {
@ -36,7 +36,8 @@ export const defaultState = {
size: 'large',
showTitle: false,
showMonitored: true,
showQualityProfile: true
showQualityProfile: true,
showSearchAction: false
},
overviewOptions: {
@ -48,7 +49,12 @@ export const defaultState = {
showAdded: false,
showAlbumCount: true,
showPath: false,
showSizeOnDisk: false
showSizeOnDisk: false,
showSearchAction: false
},
tableOptions: {
showSearchAction: false
},
columns: [
@ -337,7 +343,8 @@ export const persistState = [
'artistIndex.columns',
'artistIndex.posterOptions',
'artistIndex.bannerOptions',
'artistIndex.overviewOptions'
'artistIndex.overviewOptions',
'artistIndex.tableOptions'
];
//