(cherry picked from commit 6fce6c2bbcb1395a48f89d41d209b0ebe96042ee)pull/4254/head
parent
093d6c3c26
commit
f2b513c081
@ -1,36 +0,0 @@
|
||||
.pageContentBodyWrapper {
|
||||
display: flex;
|
||||
flex: 1 0 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.contentBody {
|
||||
composes: contentBody from '~Components/Page/PageContentBody.css';
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tableInnerContentBody {
|
||||
composes: innerContentBody from '~Components/Page/PageContentBody.css';
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.contentBodyContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointSmall) {
|
||||
.pageContentBodyWrapper {
|
||||
flex-basis: auto;
|
||||
}
|
||||
|
||||
.contentBody {
|
||||
flex-basis: 1px;
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'contentBody': string;
|
||||
'contentBodyContainer': string;
|
||||
'pageContentBodyWrapper': string;
|
||||
'tableInnerContentBody': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
export default cssExports;
|
@ -1,440 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { CellMeasurer, CellMeasurerCache } from 'react-virtualized';
|
||||
import NoArtist from 'Artist/NoArtist';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import PageJumpBar from 'Components/Page/PageJumpBar';
|
||||
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import VirtualTable from 'Components/Table/VirtualTable';
|
||||
import VirtualTableRow from 'Components/Table/VirtualTableRow';
|
||||
import { align, sortDirections } from 'Helpers/Props';
|
||||
import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter';
|
||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||
import selectAll from 'Utilities/Table/selectAll';
|
||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||
import AlbumStudioFilterModalConnector from './AlbumStudioFilterModalConnector';
|
||||
import AlbumStudioFooter from './AlbumStudioFooter';
|
||||
import AlbumStudioRowConnector from './AlbumStudioRowConnector';
|
||||
import AlbumStudioTableHeader from './AlbumStudioTableHeader';
|
||||
import styles from './AlbumStudio.css';
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: 'status',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'sortName',
|
||||
label: () => translate('Name'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'albumCount',
|
||||
label: () => translate('Albums'),
|
||||
isSortable: false,
|
||||
isVisible: true
|
||||
}
|
||||
];
|
||||
|
||||
class AlbumStudio extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.scrollerRef = React.createRef();
|
||||
|
||||
this.state = {
|
||||
estimatedRowSize: 100,
|
||||
jumpBarItems: { order: [] },
|
||||
scrollIndex: null,
|
||||
jumpCount: 0,
|
||||
allSelected: false,
|
||||
allUnselected: false,
|
||||
lastToggled: null,
|
||||
selectedState: {}
|
||||
};
|
||||
|
||||
this.cache = new CellMeasurerCache({
|
||||
defaultHeight: 100,
|
||||
fixedWidth: true
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setSelectedState();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
isSaving,
|
||||
saveError
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
scrollIndex,
|
||||
jumpCount
|
||||
} = this.state;
|
||||
|
||||
if (prevProps.isSaving && !isSaving && !saveError) {
|
||||
this.onSelectAllChange({ value: false });
|
||||
}
|
||||
|
||||
// nasty hack to fix react-virtualized jumping incorrectly
|
||||
// due to variable row heights
|
||||
if (scrollIndex != null && scrollIndex > 0) {
|
||||
if (jumpCount === 0) {
|
||||
this.setState({
|
||||
scrollIndex: scrollIndex - 1,
|
||||
jumpCount: 1
|
||||
});
|
||||
} else if (jumpCount === 1) {
|
||||
this.setState({
|
||||
scrollIndex: scrollIndex + 1,
|
||||
jumpCount: 2
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
scrollIndex: null,
|
||||
jumpCount: 0
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setJumpBarItems() {
|
||||
const {
|
||||
items,
|
||||
sortKey,
|
||||
sortDirection
|
||||
} = this.props;
|
||||
|
||||
// Reset if not sorting by sortName
|
||||
if (sortKey !== 'sortName') {
|
||||
this.setState({ jumpBarItems: { order: [] } });
|
||||
return;
|
||||
}
|
||||
|
||||
const characters = _.reduce(items, (acc, item) => {
|
||||
let char = item.sortName.charAt(0);
|
||||
|
||||
if (!isNaN(char)) {
|
||||
char = '#';
|
||||
}
|
||||
|
||||
if (char in acc) {
|
||||
acc[char] = acc[char] + 1;
|
||||
} else {
|
||||
acc[char] = 1;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const order = Object.keys(characters).sort();
|
||||
|
||||
// Reverse if sorting descending
|
||||
if (sortDirection === sortDirections.DESCENDING) {
|
||||
order.reverse();
|
||||
}
|
||||
|
||||
const jumpBarItems = {
|
||||
characters,
|
||||
order
|
||||
};
|
||||
|
||||
this.setState({ jumpBarItems });
|
||||
}
|
||||
|
||||
getSelectedIds = () => {
|
||||
if (this.state.allUnselected) {
|
||||
return [];
|
||||
}
|
||||
return getSelectedIds(this.state.selectedState);
|
||||
};
|
||||
|
||||
setSelectedState = () => {
|
||||
const {
|
||||
items
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
selectedState
|
||||
} = this.state;
|
||||
|
||||
const newSelectedState = {};
|
||||
|
||||
items.forEach((artist) => {
|
||||
const isItemSelected = selectedState[artist.id];
|
||||
|
||||
if (isItemSelected) {
|
||||
newSelectedState[artist.id] = isItemSelected;
|
||||
} else {
|
||||
newSelectedState[artist.id] = false;
|
||||
}
|
||||
});
|
||||
|
||||
const selectedCount = getSelectedIds(newSelectedState).length;
|
||||
const newStateCount = Object.keys(newSelectedState).length;
|
||||
let isAllSelected = false;
|
||||
let isAllUnselected = false;
|
||||
|
||||
if (selectedCount === 0) {
|
||||
isAllUnselected = true;
|
||||
} else if (selectedCount === newStateCount) {
|
||||
isAllSelected = true;
|
||||
}
|
||||
|
||||
this.setState({ selectedState: newSelectedState, allSelected: isAllSelected, allUnselected: isAllUnselected });
|
||||
};
|
||||
|
||||
estimateRowHeight = (width) => {
|
||||
const {
|
||||
albumCount,
|
||||
items
|
||||
} = this.props;
|
||||
|
||||
if (albumCount === undefined || albumCount === 0 || items.length === 0) {
|
||||
return 100;
|
||||
}
|
||||
|
||||
// guess 250px per album entry
|
||||
// available width is total width less 186px for select, status etc
|
||||
const cols = Math.max(Math.floor((width - 186) / 250), 1);
|
||||
const albumsPerArtist = albumCount / items.length;
|
||||
const albumRowsPerArtist = albumsPerArtist / cols;
|
||||
|
||||
// each row is 23px per album row plus 16px padding
|
||||
return albumRowsPerArtist * 23 + 16;
|
||||
};
|
||||
|
||||
rowRenderer = ({ key, rowIndex, parent, style }) => {
|
||||
const {
|
||||
items
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
selectedState
|
||||
} = this.state;
|
||||
|
||||
const item = items[rowIndex];
|
||||
|
||||
return (
|
||||
<CellMeasurer
|
||||
key={key}
|
||||
cache={this.cache}
|
||||
parent={parent}
|
||||
columnIndex={0}
|
||||
rowIndex={rowIndex}
|
||||
>
|
||||
{({ registerChild }) => (
|
||||
<VirtualTableRow
|
||||
ref={registerChild}
|
||||
style={style}
|
||||
>
|
||||
<AlbumStudioRowConnector
|
||||
key={item.id}
|
||||
artistId={item.id}
|
||||
isSelected={selectedState[item.id]}
|
||||
onSelectedChange={this.onSelectedChange}
|
||||
/>
|
||||
</VirtualTableRow>
|
||||
)}
|
||||
</CellMeasurer>
|
||||
);
|
||||
};
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onSelectAllChange = ({ value }) => {
|
||||
this.setState(selectAll(this.state.selectedState, value));
|
||||
};
|
||||
|
||||
onSelectedChange = ({ id, value, shiftKey = false }) => {
|
||||
this.setState((state) => {
|
||||
return toggleSelected(state, this.props.items, id, value, shiftKey);
|
||||
});
|
||||
};
|
||||
|
||||
onSelectAllPress = () => {
|
||||
this.onSelectAllChange({ value: !this.state.allSelected });
|
||||
};
|
||||
|
||||
onUpdateSelectedPress = (changes) => {
|
||||
this.props.onUpdateSelectedPress({
|
||||
artistIds: this.getSelectedIds(),
|
||||
...changes
|
||||
});
|
||||
};
|
||||
|
||||
onJumpBarItemPress = (jumpToCharacter) => {
|
||||
const scrollIndex = getIndexOfFirstCharacter(this.props.items, jumpToCharacter);
|
||||
|
||||
if (scrollIndex != null) {
|
||||
this.setState({ scrollIndex });
|
||||
}
|
||||
};
|
||||
|
||||
onGridRecompute = (width) => {
|
||||
this.setJumpBarItems();
|
||||
this.setSelectedState();
|
||||
this.setState({ estimatedRowSize: this.estimateRowHeight(width) });
|
||||
this.cache.clearAll();
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
totalItems,
|
||||
items,
|
||||
selectedFilterKey,
|
||||
filters,
|
||||
customFilters,
|
||||
sortKey,
|
||||
sortDirection,
|
||||
isSaving,
|
||||
saveError,
|
||||
isSmallScreen,
|
||||
onSortPress,
|
||||
onFilterSelect
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
allSelected,
|
||||
allUnselected,
|
||||
estimatedRowSize,
|
||||
jumpBarItems,
|
||||
scrollIndex
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<PageContent title={translate('AlbumStudio')}>
|
||||
<PageToolbar>
|
||||
<PageToolbarSection />
|
||||
<PageToolbarSection alignContent={align.RIGHT}>
|
||||
<FilterMenu
|
||||
alignMenu={align.RIGHT}
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
filters={filters}
|
||||
customFilters={customFilters}
|
||||
filterModalConnectorComponent={AlbumStudioFilterModalConnector}
|
||||
onFilterSelect={onFilterSelect}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
</PageToolbar>
|
||||
|
||||
<div className={styles.pageContentBodyWrapper}>
|
||||
<PageContentBody
|
||||
ref={this.scrollerRef}
|
||||
className={styles.contentBody}
|
||||
innerClassName={styles.innerContentBody}
|
||||
>
|
||||
{
|
||||
isFetching && !isPopulated &&
|
||||
<LoadingIndicator />
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>{getErrorMessage(error, 'Failed to load artist from API')}</div>
|
||||
}
|
||||
|
||||
{
|
||||
!error &&
|
||||
isPopulated &&
|
||||
!!items.length &&
|
||||
this.scrollerRef.current ?
|
||||
<div className={styles.contentBodyContainer}>
|
||||
<VirtualTable
|
||||
items={items}
|
||||
scrollIndex={scrollIndex}
|
||||
columns={columns}
|
||||
scroller={this.scrollerRef.current}
|
||||
isSmallScreen={isSmallScreen}
|
||||
overscanRowCount={5}
|
||||
rowRenderer={this.rowRenderer}
|
||||
header={
|
||||
<AlbumStudioTableHeader
|
||||
columns={columns}
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onSortPress={onSortPress}
|
||||
allSelected={allSelected}
|
||||
allUnselected={allUnselected}
|
||||
onSelectAllChange={this.onSelectAllChange}
|
||||
/>
|
||||
}
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
deferredMeasurementCache={this.cache}
|
||||
rowHeight={this.cache.rowHeight}
|
||||
estimatedRowSize={estimatedRowSize}
|
||||
onRecompute={this.onGridRecompute}
|
||||
/>
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
!error && isPopulated && !items.length &&
|
||||
<NoArtist totalItems={totalItems} />
|
||||
}
|
||||
</PageContentBody>
|
||||
|
||||
{
|
||||
isPopulated && !!jumpBarItems.order.length &&
|
||||
<PageJumpBar
|
||||
items={jumpBarItems}
|
||||
onItemPress={this.onJumpBarItemPress}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
<AlbumStudioFooter
|
||||
selectedCount={this.getSelectedIds().length}
|
||||
isSaving={isSaving}
|
||||
saveError={saveError}
|
||||
onUpdateSelectedPress={this.onUpdateSelectedPress}
|
||||
/>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AlbumStudio.propTypes = {
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
totalItems: PropTypes.number.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
albumCount: PropTypes.number.isRequired,
|
||||
sortKey: PropTypes.string,
|
||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
||||
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
saveError: PropTypes.object,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
onSortPress: PropTypes.func.isRequired,
|
||||
onFilterSelect: PropTypes.func.isRequired,
|
||||
onUpdateSelectedPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default AlbumStudio;
|
@ -1,39 +0,0 @@
|
||||
.album {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
overflow: hidden;
|
||||
margin: 2px 4px;
|
||||
border: 1px solid var(--borderColor);
|
||||
border-radius: 4px;
|
||||
background-color: var(--albumBackgroundColor);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.info {
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.albumType {
|
||||
padding: 0 4px;
|
||||
border-width: 0 1px;
|
||||
border-style: solid;
|
||||
border-color: var(--borderColor);
|
||||
background-color: var(--albumBackgroundColor);
|
||||
color: var(--defaultColor);
|
||||
}
|
||||
|
||||
.tracks {
|
||||
padding: 0 4px;
|
||||
background-color: var(--trackBackgroundColor);
|
||||
color: var(--defaultColor);
|
||||
}
|
||||
|
||||
.allTracks {
|
||||
background-color: color(#27c24c saturation(-25%));
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.missingWanted {
|
||||
background-color: color(#f05050 saturation(-20%));
|
||||
color: var(--white);
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'album': string;
|
||||
'albumType': string;
|
||||
'allTracks': string;
|
||||
'info': string;
|
||||
'missingWanted': string;
|
||||
'tracks': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
export default cssExports;
|
@ -1,102 +0,0 @@
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './AlbumStudioAlbum.css';
|
||||
|
||||
class AlbumStudioAlbum extends Component {
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onAlbumMonitoredPress = () => {
|
||||
const {
|
||||
id,
|
||||
monitored
|
||||
} = this.props;
|
||||
|
||||
this.props.onAlbumMonitoredPress(id, !monitored);
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
title,
|
||||
disambiguation,
|
||||
albumType,
|
||||
monitored,
|
||||
statistics,
|
||||
isSaving
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
trackFileCount,
|
||||
totalTrackCount,
|
||||
percentOfTracks
|
||||
} = statistics;
|
||||
|
||||
return (
|
||||
<div className={styles.album}>
|
||||
<div className={styles.info}>
|
||||
<MonitorToggleButton
|
||||
monitored={monitored}
|
||||
isSaving={isSaving}
|
||||
onPress={this.onAlbumMonitoredPress}
|
||||
/>
|
||||
|
||||
<span>
|
||||
{
|
||||
disambiguation ? `${title} (${disambiguation})` : `${title}`
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.albumType}>
|
||||
<span>
|
||||
{
|
||||
`${albumType}`
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={classNames(
|
||||
styles.tracks,
|
||||
percentOfTracks < 100 && monitored && styles.missingWanted,
|
||||
percentOfTracks === 100 && styles.allTracks
|
||||
)}
|
||||
title={translate('TrackFileCounttotalTrackCountTracksDownloadedInterp', [trackFileCount, totalTrackCount])}
|
||||
>
|
||||
{
|
||||
totalTrackCount === 0 ? '0/0' : `${trackFileCount}/${totalTrackCount}`
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AlbumStudioAlbum.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
disambiguation: PropTypes.string,
|
||||
albumType: PropTypes.string.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
statistics: PropTypes.object.isRequired,
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
onAlbumMonitoredPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
AlbumStudioAlbum.defaultProps = {
|
||||
isSaving: false,
|
||||
statistics: {
|
||||
trackFileCount: 0,
|
||||
totalTrackCount: 0,
|
||||
percentOfTracks: 0
|
||||
}
|
||||
};
|
||||
|
||||
export default AlbumStudioAlbum;
|
@ -1,116 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { clearAlbums, fetchAlbums } from 'Store/Actions/albumActions';
|
||||
import { saveAlbumStudio, setAlbumStudioFilter, setAlbumStudioSort } from 'Store/Actions/albumStudioActions';
|
||||
import createArtistClientSideCollectionItemsSelector from 'Store/Selectors/createArtistClientSideCollectionItemsSelector';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import AlbumStudio from './AlbumStudio';
|
||||
|
||||
function createAlbumFetchStateSelector() {
|
||||
return createSelector(
|
||||
(state) => state.albums.items.length,
|
||||
(state) => state.albums.isFetching,
|
||||
(state) => state.albums.isPopulated,
|
||||
(length, isFetching, isPopulated) => {
|
||||
const albumCount = (!isFetching && isPopulated) ? length : 0;
|
||||
return {
|
||||
albumCount,
|
||||
isFetching,
|
||||
isPopulated
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createAlbumFetchStateSelector(),
|
||||
createArtistClientSideCollectionItemsSelector('albumStudio'),
|
||||
createDimensionsSelector(),
|
||||
(albums, artist, dimensionsState) => {
|
||||
const isPopulated = albums.isPopulated && artist.isPopulated;
|
||||
const isFetching = artist.isFetching || albums.isFetching;
|
||||
return {
|
||||
...artist,
|
||||
isPopulated,
|
||||
isFetching,
|
||||
albumCount: albums.albumCount,
|
||||
isSmallScreen: dimensionsState.isSmallScreen
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchAlbums,
|
||||
clearAlbums,
|
||||
setAlbumStudioSort,
|
||||
setAlbumStudioFilter,
|
||||
saveAlbumStudio
|
||||
};
|
||||
|
||||
class AlbumStudioConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
this.populate();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unpopulate();
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
populate = () => {
|
||||
this.props.fetchAlbums();
|
||||
};
|
||||
|
||||
unpopulate = () => {
|
||||
this.props.clearAlbums();
|
||||
};
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onSortPress = (sortKey) => {
|
||||
this.props.setAlbumStudioSort({ sortKey });
|
||||
};
|
||||
|
||||
onFilterSelect = (selectedFilterKey) => {
|
||||
this.props.setAlbumStudioFilter({ selectedFilterKey });
|
||||
};
|
||||
|
||||
onUpdateSelectedPress = (payload) => {
|
||||
this.props.saveAlbumStudio(payload);
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<AlbumStudio
|
||||
{...this.props}
|
||||
onSortPress={this.onSortPress}
|
||||
onFilterSelect={this.onFilterSelect}
|
||||
onUpdateSelectedPress={this.onUpdateSelectedPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AlbumStudioConnector.propTypes = {
|
||||
setAlbumStudioSort: PropTypes.func.isRequired,
|
||||
setAlbumStudioFilter: PropTypes.func.isRequired,
|
||||
fetchAlbums: PropTypes.func.isRequired,
|
||||
clearAlbums: PropTypes.func.isRequired,
|
||||
saveAlbumStudio: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(AlbumStudioConnector);
|
@ -1,24 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import FilterModal from 'Components/Filter/FilterModal';
|
||||
import { setAlbumStudioFilter } from 'Store/Actions/albumStudioActions';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.artist.items,
|
||||
(state) => state.albumStudio.filterBuilderProps,
|
||||
(sectionItems, filterBuilderProps) => {
|
||||
return {
|
||||
sectionItems,
|
||||
filterBuilderProps,
|
||||
customFilterType: 'albumStudio'
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
dispatchSetFilter: setAlbumStudioFilter
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(FilterModal);
|
@ -1,14 +0,0 @@
|
||||
.inputContainer {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-bottom: 3px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.updateSelectedButton {
|
||||
composes: button from '~Components/Link/SpinnerButton.css';
|
||||
|
||||
height: 35px;
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'inputContainer': string;
|
||||
'label': string;
|
||||
'updateSelectedButton': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
export default cssExports;
|
@ -1,174 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import MonitorAlbumsSelectInput from 'Components/Form/MonitorAlbumsSelectInput';
|
||||
import MonitorNewItemsSelectInput from 'Components/Form/MonitorNewItemsSelectInput';
|
||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||
import PageContentFooter from 'Components/Page/PageContentFooter';
|
||||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './AlbumStudioFooter.css';
|
||||
|
||||
const NO_CHANGE = 'noChange';
|
||||
|
||||
class AlbumStudioFooter extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
monitored: NO_CHANGE,
|
||||
monitor: NO_CHANGE,
|
||||
monitorNewItems: NO_CHANGE
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
isSaving,
|
||||
saveError
|
||||
} = this.props;
|
||||
|
||||
if (prevProps.isSaving && !isSaving && !saveError) {
|
||||
this.setState({
|
||||
monitored: NO_CHANGE,
|
||||
monitor: NO_CHANGE,
|
||||
monitorNewItems: NO_CHANGE
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onInputChange = ({ name, value }) => {
|
||||
this.setState({ [name]: value });
|
||||
};
|
||||
|
||||
onUpdateSelectedPress = () => {
|
||||
const {
|
||||
monitor,
|
||||
monitored,
|
||||
monitorNewItems
|
||||
} = this.state;
|
||||
|
||||
const changes = {};
|
||||
|
||||
if (monitored !== NO_CHANGE) {
|
||||
changes.monitored = monitored === 'monitored';
|
||||
}
|
||||
|
||||
if (monitor !== NO_CHANGE) {
|
||||
changes.monitor = monitor;
|
||||
}
|
||||
|
||||
if (monitorNewItems !== NO_CHANGE) {
|
||||
changes.monitorNewItems = monitorNewItems;
|
||||
}
|
||||
|
||||
this.props.onUpdateSelectedPress(changes);
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
selectedCount,
|
||||
isSaving
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
monitored,
|
||||
monitor,
|
||||
monitorNewItems
|
||||
} = this.state;
|
||||
|
||||
const monitoredOptions = [
|
||||
{ key: NO_CHANGE, value: translate('NoChange'), disabled: true },
|
||||
{ key: 'monitored', value: translate('Monitored') },
|
||||
{ key: 'unmonitored', value: translate('Unmonitored') }
|
||||
];
|
||||
|
||||
const noChanges = monitored === NO_CHANGE &&
|
||||
monitor === NO_CHANGE &&
|
||||
monitorNewItems === NO_CHANGE;
|
||||
|
||||
return (
|
||||
<PageContentFooter>
|
||||
<div className={styles.inputContainer}>
|
||||
<div className={styles.label}>
|
||||
{translate('MonitorArtist')}
|
||||
</div>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name="monitored"
|
||||
value={monitored}
|
||||
values={monitoredOptions}
|
||||
isDisabled={!selectedCount}
|
||||
onChange={this.onInputChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.inputContainer}>
|
||||
<div className={styles.label}>
|
||||
{translate('MonitorExistingAlbums')}
|
||||
</div>
|
||||
|
||||
<MonitorAlbumsSelectInput
|
||||
name="monitor"
|
||||
value={monitor}
|
||||
includeNoChange={true}
|
||||
includeNoChangeDisabled={false}
|
||||
isDisabled={!selectedCount}
|
||||
onChange={this.onInputChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.inputContainer}>
|
||||
<div className={styles.label}>
|
||||
{translate('MonitorNewAlbums')}
|
||||
</div>
|
||||
|
||||
<MonitorNewItemsSelectInput
|
||||
name="monitorNewItems"
|
||||
value={monitorNewItems}
|
||||
includeNoChange={true}
|
||||
includeNoChangeDisabled={false}
|
||||
isDisabled={!selectedCount}
|
||||
onChange={this.onInputChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className={styles.label}>
|
||||
{translate('CountArtistsSelected', { count: selectedCount })}
|
||||
</div>
|
||||
|
||||
<SpinnerButton
|
||||
className={styles.updateSelectedButton}
|
||||
kind={kinds.PRIMARY}
|
||||
isSpinning={isSaving}
|
||||
isDisabled={!selectedCount || noChanges}
|
||||
onPress={this.onUpdateSelectedPress}
|
||||
>
|
||||
{translate('UpdateSelected')}
|
||||
</SpinnerButton>
|
||||
</div>
|
||||
</PageContentFooter>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AlbumStudioFooter.propTypes = {
|
||||
selectedCount: PropTypes.number.isRequired,
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
saveError: PropTypes.object,
|
||||
onUpdateSelectedPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default AlbumStudioFooter;
|
@ -1,41 +0,0 @@
|
||||
.cell {
|
||||
composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css';
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.selectCell {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.title {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
flex-shrink: 0;
|
||||
min-width: 110px;
|
||||
}
|
||||
|
||||
.albums {
|
||||
composes: cell;
|
||||
|
||||
display: flex;
|
||||
flex-grow: 4;
|
||||
flex-wrap: wrap;
|
||||
min-width: 400px;
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'albums': string;
|
||||
'cell': string;
|
||||
'selectCell': string;
|
||||
'status': string;
|
||||
'title': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
export default cssExports;
|
@ -1,95 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import ArtistNameLink from 'Artist/ArtistNameLink';
|
||||
import ArtistStatusCell from 'Artist/Index/Table/ArtistStatusCell';
|
||||
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
|
||||
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
|
||||
import AlbumStudioAlbum from './AlbumStudioAlbum';
|
||||
import styles from './AlbumStudioRow.css';
|
||||
|
||||
class AlbumStudioRow extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
artistId,
|
||||
status,
|
||||
foreignArtistId,
|
||||
artistName,
|
||||
artistType,
|
||||
monitored,
|
||||
albums,
|
||||
isSaving,
|
||||
isSelected,
|
||||
onSelectedChange,
|
||||
onArtistMonitoredPress,
|
||||
onAlbumMonitoredPress
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<VirtualTableSelectCell
|
||||
className={styles.selectCell}
|
||||
id={artistId}
|
||||
isSelected={isSelected}
|
||||
onSelectedChange={onSelectedChange}
|
||||
isDisabled={false}
|
||||
/>
|
||||
|
||||
<ArtistStatusCell
|
||||
className={styles.status}
|
||||
artistType={artistType}
|
||||
monitored={monitored}
|
||||
status={status}
|
||||
isSaving={isSaving}
|
||||
onMonitoredPress={onArtistMonitoredPress}
|
||||
component={VirtualTableRowCell}
|
||||
/>
|
||||
|
||||
<VirtualTableRowCell className={styles.title}>
|
||||
<ArtistNameLink
|
||||
foreignArtistId={foreignArtistId}
|
||||
artistName={artistName}
|
||||
/>
|
||||
</VirtualTableRowCell>
|
||||
|
||||
<VirtualTableRowCell className={styles.albums}>
|
||||
{
|
||||
albums.map((album) => {
|
||||
return (
|
||||
<AlbumStudioAlbum
|
||||
key={album.id}
|
||||
{...album}
|
||||
onAlbumMonitoredPress={onAlbumMonitoredPress}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</VirtualTableRowCell>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AlbumStudioRow.propTypes = {
|
||||
artistId: PropTypes.number.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
foreignArtistId: PropTypes.string.isRequired,
|
||||
artistName: PropTypes.string.isRequired,
|
||||
artistType: PropTypes.string,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
albums: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
isSelected: PropTypes.bool,
|
||||
onSelectedChange: PropTypes.func.isRequired,
|
||||
onArtistMonitoredPress: PropTypes.func.isRequired,
|
||||
onAlbumMonitoredPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
AlbumStudioRow.defaultProps = {
|
||||
isSaving: false
|
||||
};
|
||||
|
||||
export default AlbumStudioRow;
|
@ -1,94 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { toggleAlbumsMonitored } from 'Store/Actions/albumActions';
|
||||
import { toggleArtistMonitored } from 'Store/Actions/artistActions';
|
||||
import createArtistSelector from 'Store/Selectors/createArtistSelector';
|
||||
import AlbumStudioRow from './AlbumStudioRow';
|
||||
|
||||
// Use a const to share the reselect cache between instances
|
||||
const getAlbumMap = createSelector(
|
||||
(state) => state.albums.items,
|
||||
(albums) => {
|
||||
return albums.reduce((acc, curr) => {
|
||||
(acc[curr.artistId] = acc[curr.artistId] || []).push(curr);
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
);
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createArtistSelector(),
|
||||
getAlbumMap,
|
||||
(artist, albumMap) => {
|
||||
const albumsInArtist = albumMap.hasOwnProperty(artist.id) ? albumMap[artist.id] : [];
|
||||
const sortedAlbums = _.orderBy(albumsInArtist, 'releaseDate', 'desc');
|
||||
|
||||
return {
|
||||
...artist,
|
||||
artistId: artist.id,
|
||||
artistName: artist.artistName,
|
||||
monitored: artist.monitored,
|
||||
status: artist.status,
|
||||
isSaving: artist.isSaving,
|
||||
albums: sortedAlbums
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
toggleArtistMonitored,
|
||||
toggleAlbumsMonitored
|
||||
};
|
||||
|
||||
class AlbumStudioRowConnector extends Component {
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onArtistMonitoredPress = () => {
|
||||
const {
|
||||
artistId,
|
||||
monitored
|
||||
} = this.props;
|
||||
|
||||
this.props.toggleArtistMonitored({
|
||||
artistId,
|
||||
monitored: !monitored
|
||||
});
|
||||
};
|
||||
|
||||
onAlbumMonitoredPress = (albumId, monitored) => {
|
||||
const albumIds = [albumId];
|
||||
this.props.toggleAlbumsMonitored({
|
||||
albumIds,
|
||||
monitored
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<AlbumStudioRow
|
||||
{...this.props}
|
||||
onArtistMonitoredPress={this.onArtistMonitoredPress}
|
||||
onAlbumMonitoredPress={this.onAlbumMonitoredPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AlbumStudioRowConnector.propTypes = {
|
||||
artistId: PropTypes.number.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
toggleArtistMonitored: PropTypes.func.isRequired,
|
||||
toggleAlbumsMonitored: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(AlbumStudioRowConnector);
|
@ -1,18 +0,0 @@
|
||||
.status {
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
||||
flex: 0 0 60px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.sortName {
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
||||
flex: 0 0 110px;
|
||||
}
|
||||
|
||||
.albumCount {
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
||||
padding: 12px;
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'albumCount': string;
|
||||
'sortName': string;
|
||||
'status': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
export default cssExports;
|
@ -1,61 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import VirtualTableHeader from 'Components/Table/VirtualTableHeader';
|
||||
import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell';
|
||||
import VirtualTableSelectAllHeaderCell from 'Components/Table/VirtualTableSelectAllHeaderCell';
|
||||
import styles from './AlbumStudioTableHeader.css';
|
||||
|
||||
function AlbumStudioTableHeader(props) {
|
||||
const {
|
||||
columns,
|
||||
allSelected,
|
||||
allUnselected,
|
||||
onSelectAllChange,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<VirtualTableHeader>
|
||||
<VirtualTableSelectAllHeaderCell
|
||||
allSelected={allSelected}
|
||||
allUnselected={allUnselected}
|
||||
onSelectAllChange={onSelectAllChange}
|
||||
/>
|
||||
{
|
||||
columns.map((column) => {
|
||||
const {
|
||||
name,
|
||||
label,
|
||||
isSortable,
|
||||
isVisible
|
||||
} = column;
|
||||
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<VirtualTableHeaderCell
|
||||
key={name}
|
||||
className={styles[name]}
|
||||
name={name}
|
||||
isSortable={isSortable}
|
||||
{...otherProps}
|
||||
>
|
||||
{typeof label === 'function' ? label() : label}
|
||||
</VirtualTableHeaderCell>
|
||||
);
|
||||
})
|
||||
}
|
||||
</VirtualTableHeader>
|
||||
);
|
||||
}
|
||||
|
||||
AlbumStudioTableHeader.propTypes = {
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
allSelected: PropTypes.bool.isRequired,
|
||||
allUnselected: PropTypes.bool.isRequired,
|
||||
onSelectAllChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default AlbumStudioTableHeader;
|
@ -1,167 +0,0 @@
|
||||
import { createAction } from 'redux-actions';
|
||||
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import { fetchAlbums } from './albumActions';
|
||||
import { filterPredicates, filters } from './artistActions';
|
||||
import { set } from './baseActions';
|
||||
import createHandleActions from './Creators/createHandleActions';
|
||||
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
|
||||
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
|
||||
|
||||
//
|
||||
// Variables
|
||||
|
||||
export const section = 'albumStudio';
|
||||
|
||||
//
|
||||
// State
|
||||
|
||||
export const defaultState = {
|
||||
isSaving: false,
|
||||
saveError: null,
|
||||
sortKey: 'sortName',
|
||||
sortDirection: sortDirections.ASCENDING,
|
||||
secondarySortKey: 'sortName',
|
||||
secondarySortDirection: sortDirections.ASCENDING,
|
||||
selectedFilterKey: 'all',
|
||||
filters,
|
||||
filterPredicates,
|
||||
|
||||
filterBuilderProps: [
|
||||
{
|
||||
name: 'monitored',
|
||||
label: () => translate('Monitored'),
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.BOOL
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
label: () => translate('Status'),
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.ARTIST_STATUS
|
||||
},
|
||||
{
|
||||
name: 'artistType',
|
||||
label: () => translate('ArtistType'),
|
||||
type: filterBuilderTypes.EXACT
|
||||
},
|
||||
{
|
||||
name: 'qualityProfileId',
|
||||
label: () => translate('QualityProfile'),
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.QUALITY_PROFILE
|
||||
},
|
||||
{
|
||||
name: 'metadataProfileId',
|
||||
label: () => translate('MetadataProfile'),
|
||||
type: filterBuilderTypes.EXACT,
|
||||
valueType: filterBuilderValueTypes.METADATA_PROFILE
|
||||
},
|
||||
{
|
||||
name: 'rootFolderPath',
|
||||
label: () => translate('RootFolderPath'),
|
||||
type: filterBuilderTypes.EXACT
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
label: () => translate('Tags'),
|
||||
type: filterBuilderTypes.ARRAY,
|
||||
valueType: filterBuilderValueTypes.TAG
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const persistState = [
|
||||
'albumStudio.sortKey',
|
||||
'albumStudio.sortDirection',
|
||||
'albumStudio.selectedFilterKey',
|
||||
'albumStudio.customFilters'
|
||||
];
|
||||
|
||||
//
|
||||
// Actions Types
|
||||
|
||||
export const SET_ALBUM_STUDIO_SORT = 'albumStudio/setAlbumStudioSort';
|
||||
export const SET_ALBUM_STUDIO_FILTER = 'albumStudio/setAlbumStudioFilter';
|
||||
export const SAVE_ALBUM_STUDIO = 'albumStudio/saveAlbumStudio';
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
||||
export const setAlbumStudioSort = createAction(SET_ALBUM_STUDIO_SORT);
|
||||
export const setAlbumStudioFilter = createAction(SET_ALBUM_STUDIO_FILTER);
|
||||
export const saveAlbumStudio = createThunk(SAVE_ALBUM_STUDIO);
|
||||
|
||||
//
|
||||
// Action Handlers
|
||||
|
||||
export const actionHandlers = handleThunks({
|
||||
|
||||
[SAVE_ALBUM_STUDIO]: function(getState, payload, dispatch) {
|
||||
const {
|
||||
artistIds,
|
||||
monitor,
|
||||
monitored,
|
||||
monitorNewItems
|
||||
} = payload;
|
||||
|
||||
const artists = [];
|
||||
|
||||
artistIds.forEach((id) => {
|
||||
const artistsToUpdate = { id };
|
||||
|
||||
if (payload.hasOwnProperty('monitored')) {
|
||||
artistsToUpdate.monitored = monitored;
|
||||
}
|
||||
|
||||
artists.push(artistsToUpdate);
|
||||
});
|
||||
|
||||
dispatch(set({
|
||||
section,
|
||||
isSaving: true
|
||||
}));
|
||||
|
||||
const promise = createAjaxRequest({
|
||||
url: '/albumStudio',
|
||||
method: 'POST',
|
||||
data: JSON.stringify({
|
||||
artist: artists,
|
||||
monitoringOptions: { monitor },
|
||||
monitorNewItems
|
||||
}),
|
||||
dataType: 'json'
|
||||
}).request;
|
||||
|
||||
promise.done((data) => {
|
||||
dispatch(fetchAlbums());
|
||||
|
||||
dispatch(set({
|
||||
section,
|
||||
isSaving: false,
|
||||
saveError: null
|
||||
}));
|
||||
});
|
||||
|
||||
promise.fail((xhr) => {
|
||||
dispatch(set({
|
||||
section,
|
||||
isSaving: false,
|
||||
saveError: xhr
|
||||
}));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// Reducers
|
||||
|
||||
export const reducers = createHandleActions({
|
||||
|
||||
[SET_ALBUM_STUDIO_SORT]: createSetClientSideCollectionSortReducer(section),
|
||||
[SET_ALBUM_STUDIO_FILTER]: createSetClientSideCollectionFilterReducer(section)
|
||||
|
||||
}, defaultState, section);
|
||||
|
Loading…
Reference in new issue