parent
f6fc78d927
commit
566ac1a9d3
@ -1,45 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import getProgressBarKind from 'Utilities/Series/getProgressBarKind';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import ProgressBar from 'Components/ProgressBar';
|
||||
import styles from './ArtistIndexBannerProgressBar.css';
|
||||
|
||||
function ArtistIndexBannerProgressBar(props) {
|
||||
const {
|
||||
monitored,
|
||||
status,
|
||||
trackCount,
|
||||
trackFileCount,
|
||||
bannerWidth,
|
||||
detailedProgressBar
|
||||
} = props;
|
||||
|
||||
const progress = trackCount ? trackFileCount / trackCount * 100 : 100;
|
||||
const text = `${trackFileCount} / ${trackCount}`;
|
||||
|
||||
return (
|
||||
<ProgressBar
|
||||
className={styles.progressBar}
|
||||
containerClassName={styles.progress}
|
||||
progress={progress}
|
||||
kind={getProgressBarKind(status, monitored, progress)}
|
||||
size={detailedProgressBar ? sizes.MEDIUM : sizes.SMALL}
|
||||
showText={detailedProgressBar}
|
||||
text={text}
|
||||
title={detailedProgressBar ? null : text}
|
||||
width={bannerWidth}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
ArtistIndexBannerProgressBar.propTypes = {
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
trackCount: PropTypes.number.isRequired,
|
||||
trackFileCount: PropTypes.number.isRequired,
|
||||
bannerWidth: PropTypes.number.isRequired,
|
||||
detailedProgressBar: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
export default ArtistIndexBannerProgressBar;
|
@ -0,0 +1,82 @@
|
||||
$hoverScale: 1.05;
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.poster {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.posterContainer {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.link {
|
||||
composes: link from 'Components/Link/Link.css';
|
||||
|
||||
display: block;
|
||||
color: $defaultColor;
|
||||
|
||||
&:hover {
|
||||
color: $defaultColor;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.ended {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-width: 0 25px 25px 0;
|
||||
border-style: solid;
|
||||
border-color: transparent $dangerColor transparent transparent;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1 0 1px;
|
||||
overflow: hidden;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.titleRow {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.title {
|
||||
@add-mixin truncate;
|
||||
composes: link;
|
||||
|
||||
flex: 1 0 1px;
|
||||
font-weight: 300;
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.details {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.overview {
|
||||
composes: link;
|
||||
|
||||
flex: 0 1 1000px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointSmall) {
|
||||
.overview {
|
||||
display: none;
|
||||
}
|
||||
}
|
@ -0,0 +1,250 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Truncate from 'react-truncate';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import dimensions from 'Styles/Variables/dimensions';
|
||||
import fonts from 'Styles/Variables/fonts';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import Link from 'Components/Link/Link';
|
||||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||
import ArtistPoster from 'Artist/ArtistPoster';
|
||||
import EditArtistModalConnector from 'Artist/Edit/EditArtistModalConnector';
|
||||
import DeleteArtistModal from 'Artist/Delete/DeleteArtistModal';
|
||||
import ArtistIndexProgressBar from 'Artist/Index/ProgressBar/ArtistIndexProgressBar';
|
||||
import ArtistIndexOverviewInfo from './ArtistIndexOverviewInfo';
|
||||
import styles from './ArtistIndexOverview.css';
|
||||
|
||||
const columnPadding = parseInt(dimensions.artistIndexColumnPadding);
|
||||
const columnPaddingSmallScreen = parseInt(dimensions.artistIndexColumnPaddingSmallScreen);
|
||||
const defaultFontSize = parseInt(fonts.defaultFontSize);
|
||||
const lineHeight = parseFloat(fonts.lineHeight);
|
||||
|
||||
function calculateHeight(rowHeight, isSmallScreen) {
|
||||
let height = rowHeight - 45;
|
||||
|
||||
if (isSmallScreen) {
|
||||
height -= columnPaddingSmallScreen;
|
||||
} else {
|
||||
height -= columnPadding;
|
||||
}
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
class ArtistIndexOverview extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isEditArtistModalOpen: false,
|
||||
isDeleteArtistModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onEditArtistPress = () => {
|
||||
this.setState({ isEditArtistModalOpen: true });
|
||||
}
|
||||
|
||||
onEditArtistModalClose = () => {
|
||||
this.setState({ isEditArtistModalOpen: false });
|
||||
}
|
||||
|
||||
onDeleteArtistPress = () => {
|
||||
this.setState({
|
||||
isEditArtistModalOpen: false,
|
||||
isDeleteArtistModalOpen: true
|
||||
});
|
||||
}
|
||||
|
||||
onDeleteArtistModalClose = () => {
|
||||
this.setState({ isDeleteArtistModalOpen: false });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
style,
|
||||
id,
|
||||
artistName,
|
||||
overview,
|
||||
monitored,
|
||||
status,
|
||||
nameSlug,
|
||||
nextAiring,
|
||||
trackCount,
|
||||
trackFileCount,
|
||||
images,
|
||||
posterWidth,
|
||||
posterHeight,
|
||||
qualityProfile,
|
||||
overviewOptions,
|
||||
showRelativeDates,
|
||||
shortDateFormat,
|
||||
timeFormat,
|
||||
rowHeight,
|
||||
isSmallScreen,
|
||||
isRefreshingArtist,
|
||||
onRefreshArtistPress,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
isEditArtistModalOpen,
|
||||
isDeleteArtistModalOpen
|
||||
} = this.state;
|
||||
|
||||
const link = `/artist/${nameSlug}`;
|
||||
|
||||
const elementStyle = {
|
||||
width: `${posterWidth}px`,
|
||||
height: `${posterHeight}px`
|
||||
};
|
||||
|
||||
const height = calculateHeight(rowHeight, isSmallScreen);
|
||||
|
||||
return (
|
||||
<div className={styles.container} style={style}>
|
||||
<div className={styles.poster} style={elementStyle}>
|
||||
<div className={styles.posterContainer}>
|
||||
{
|
||||
status === 'ended' &&
|
||||
<div
|
||||
className={styles.ended}
|
||||
title="Ended"
|
||||
/>
|
||||
}
|
||||
|
||||
<Link
|
||||
className={styles.link}
|
||||
style={elementStyle}
|
||||
to={link}
|
||||
>
|
||||
<ArtistPoster
|
||||
className={styles.poster}
|
||||
style={elementStyle}
|
||||
images={images}
|
||||
size={250}
|
||||
lazy={false}
|
||||
overflow={true}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<ArtistIndexProgressBar
|
||||
monitored={monitored}
|
||||
status={status}
|
||||
trackCount={trackCount}
|
||||
trackFileCount={trackFileCount}
|
||||
posterWidth={posterWidth}
|
||||
detailedProgressBar={overviewOptions.detailedProgressBar}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.info}>
|
||||
<div className={styles.titleRow}>
|
||||
<Link
|
||||
className={styles.title}
|
||||
to={link}
|
||||
>
|
||||
{artistName}
|
||||
</Link>
|
||||
|
||||
<div className={styles.actions}>
|
||||
<SpinnerIconButton
|
||||
name={icons.REFRESH}
|
||||
title="Refresh artist"
|
||||
isSpinning={isRefreshingArtist}
|
||||
onPress={onRefreshArtistPress}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
name={icons.EDIT}
|
||||
title="Edit Artist"
|
||||
onPress={this.onEditArtistPress}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.details}>
|
||||
<Link
|
||||
className={styles.overview}
|
||||
style={{
|
||||
maxHeight: `${height}px`
|
||||
}}
|
||||
to={link}
|
||||
>
|
||||
<Truncate lines={Math.floor(height / (defaultFontSize * lineHeight))}>
|
||||
{overview}
|
||||
</Truncate>
|
||||
</Link>
|
||||
|
||||
<ArtistIndexOverviewInfo
|
||||
height={height}
|
||||
nextAiring={nextAiring}
|
||||
qualityProfile={qualityProfile}
|
||||
showRelativeDates={showRelativeDates}
|
||||
shortDateFormat={shortDateFormat}
|
||||
timeFormat={timeFormat}
|
||||
{...overviewOptions}
|
||||
{...otherProps}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<EditArtistModalConnector
|
||||
isOpen={isEditArtistModalOpen}
|
||||
artistId={id}
|
||||
onModalClose={this.onEditArtistModalClose}
|
||||
onDeleteArtistPress={this.onDeleteArtistPress}
|
||||
/>
|
||||
|
||||
<DeleteArtistModal
|
||||
isOpen={isDeleteArtistModalOpen}
|
||||
artistId={id}
|
||||
onModalClose={this.onDeleteArtistModalClose}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ArtistIndexOverview.propTypes = {
|
||||
style: PropTypes.object.isRequired,
|
||||
id: PropTypes.number.isRequired,
|
||||
artistName: PropTypes.string.isRequired,
|
||||
overview: PropTypes.string.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
nameSlug: PropTypes.string.isRequired,
|
||||
nextAiring: PropTypes.string,
|
||||
trackCount: PropTypes.number,
|
||||
trackFileCount: PropTypes.number,
|
||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
posterWidth: PropTypes.number.isRequired,
|
||||
posterHeight: PropTypes.number.isRequired,
|
||||
rowHeight: PropTypes.number.isRequired,
|
||||
qualityProfile: PropTypes.object.isRequired,
|
||||
overviewOptions: PropTypes.object.isRequired,
|
||||
showRelativeDates: PropTypes.bool.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
isRefreshingArtist: PropTypes.bool.isRequired,
|
||||
onRefreshArtistPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
ArtistIndexOverview.defaultProps = {
|
||||
trackCount: 0,
|
||||
trackFileCount: 0
|
||||
};
|
||||
|
||||
export default ArtistIndexOverview;
|
@ -0,0 +1,23 @@
|
||||
.infos {
|
||||
display: flex;
|
||||
flex: 0 0 250px;
|
||||
flex-direction: column;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 0 0 $artistIndexOverviewInfoRowHeight;
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-right: 5px;
|
||||
width: 25px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointSmall) {
|
||||
.infos {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,193 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import dimensions from 'Styles/Variables/dimensions';
|
||||
import Icon from 'Components/Icon';
|
||||
import styles from './ArtistIndexOverviewInfo.css';
|
||||
|
||||
const infoRowHeight = parseInt(dimensions.artistIndexOverviewInfoRowHeight);
|
||||
|
||||
function isVisible(name, show, value, sortKey, index) {
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return show || sortKey === name;
|
||||
}
|
||||
|
||||
function ArtistIndexOverviewInfo(props) {
|
||||
const {
|
||||
height,
|
||||
showQualityProfile,
|
||||
showAdded,
|
||||
showAlbumCount,
|
||||
showPath,
|
||||
showSizeOnDisk,
|
||||
nextAiring,
|
||||
qualityProfile,
|
||||
added,
|
||||
albumCount,
|
||||
path,
|
||||
sizeOnDisk,
|
||||
sortKey,
|
||||
showRelativeDates,
|
||||
shortDateFormat,
|
||||
timeFormat
|
||||
} = props;
|
||||
|
||||
let albums = '1 album';
|
||||
|
||||
if (albumCount === 0) {
|
||||
albums = 'No albums';
|
||||
} else if (albumCount > 1) {
|
||||
albums = `${albumCount} albums`;
|
||||
}
|
||||
|
||||
const maxRows = Math.floor(height / (infoRowHeight + 4));
|
||||
|
||||
return (
|
||||
<div className={styles.infos}>
|
||||
{
|
||||
!!nextAiring &&
|
||||
<div
|
||||
className={styles.info}
|
||||
title="Next Airing"
|
||||
>
|
||||
<Icon
|
||||
className={styles.icon}
|
||||
name={icons.SCHEDULED}
|
||||
size={14}
|
||||
/>
|
||||
|
||||
{
|
||||
getRelativeDate(
|
||||
nextAiring,
|
||||
shortDateFormat,
|
||||
showRelativeDates,
|
||||
{
|
||||
timeFormat,
|
||||
timeForToday: true
|
||||
}
|
||||
)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
isVisible('qualityProfileId', showQualityProfile, qualityProfile, sortKey) && maxRows > 1 &&
|
||||
<div
|
||||
className={styles.info}
|
||||
title="Quality Profile"
|
||||
>
|
||||
<Icon
|
||||
className={styles.icon}
|
||||
name={icons.PROFILE}
|
||||
size={14}
|
||||
/>
|
||||
|
||||
{qualityProfile.name}
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
isVisible('added', showAdded, added, sortKey) && maxRows > 2 &&
|
||||
<div
|
||||
className={styles.info}
|
||||
title="Date Added"
|
||||
>
|
||||
<Icon
|
||||
className={styles.icon}
|
||||
name={icons.ADD}
|
||||
size={14}
|
||||
/>
|
||||
|
||||
{
|
||||
getRelativeDate(
|
||||
added,
|
||||
shortDateFormat,
|
||||
showRelativeDates,
|
||||
{
|
||||
timeFormat,
|
||||
timeForToday: true
|
||||
}
|
||||
)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
isVisible('albumCount', showAlbumCount, albumCount, sortKey) && maxRows > 3 &&
|
||||
<div
|
||||
className={styles.info}
|
||||
title="Album Count"
|
||||
>
|
||||
<Icon
|
||||
className={styles.icon}
|
||||
name={icons.CIRCLE}
|
||||
size={14}
|
||||
/>
|
||||
|
||||
{albums}
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
isVisible('path', showPath, path, sortKey) && maxRows > 4 &&
|
||||
<div
|
||||
className={styles.info}
|
||||
title="Path"
|
||||
>
|
||||
<Icon
|
||||
className={styles.icon}
|
||||
name={icons.FOLDER}
|
||||
size={14}
|
||||
/>
|
||||
|
||||
{path}
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
isVisible('sizeOnDisk', showSizeOnDisk, sizeOnDisk, sortKey) && maxRows > 5 &&
|
||||
<div
|
||||
className={styles.info}
|
||||
title="Size on Disk"
|
||||
>
|
||||
<Icon
|
||||
className={styles.icon}
|
||||
name={icons.DRIVE}
|
||||
size={14}
|
||||
/>
|
||||
|
||||
{formatBytes(sizeOnDisk)}
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ArtistIndexOverviewInfo.propTypes = {
|
||||
height: PropTypes.number.isRequired,
|
||||
showNetwork: PropTypes.bool.isRequired,
|
||||
showQualityProfile: PropTypes.bool.isRequired,
|
||||
showAdded: PropTypes.bool.isRequired,
|
||||
showAlbumCount: PropTypes.bool.isRequired,
|
||||
showPath: PropTypes.bool.isRequired,
|
||||
showSizeOnDisk: PropTypes.bool.isRequired,
|
||||
nextAiring: PropTypes.string,
|
||||
qualityProfile: PropTypes.object.isRequired,
|
||||
previousAiring: PropTypes.string,
|
||||
added: PropTypes.string,
|
||||
albumCount: PropTypes.number.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
sizeOnDisk: PropTypes.number,
|
||||
sortKey: PropTypes.string.isRequired,
|
||||
showRelativeDates: PropTypes.bool.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default ArtistIndexOverviewInfo;
|
@ -0,0 +1,3 @@
|
||||
.grid {
|
||||
flex: 1 0 auto;
|
||||
}
|
@ -0,0 +1,278 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Measure from 'react-measure';
|
||||
import { Grid, WindowScroller } from 'react-virtualized';
|
||||
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
|
||||
import dimensions from 'Styles/Variables/dimensions';
|
||||
import { sortDirections } from 'Helpers/Props';
|
||||
import ArtistIndexItemConnector from 'Artist/Index/ArtistIndexItemConnector';
|
||||
import ArtistIndexOverview from './ArtistIndexOverview';
|
||||
import styles from './ArtistIndexOverviews.css';
|
||||
|
||||
// Poster container dimensions
|
||||
const columnPadding = parseInt(dimensions.artistIndexColumnPadding);
|
||||
const columnPaddingSmallScreen = parseInt(dimensions.artistIndexColumnPaddingSmallScreen);
|
||||
const progressBarHeight = parseInt(dimensions.progressBarSmallHeight);
|
||||
const detailedProgressBarHeight = parseInt(dimensions.progressBarMediumHeight);
|
||||
|
||||
function calculatePosterWidth(posterSize, isSmallScreen) {
|
||||
const maxiumPosterWidth = isSmallScreen ? 192 : 202;
|
||||
|
||||
if (posterSize === 'large') {
|
||||
return maxiumPosterWidth;
|
||||
}
|
||||
|
||||
if (posterSize === 'medium') {
|
||||
return Math.floor(maxiumPosterWidth * 0.75);
|
||||
}
|
||||
|
||||
return Math.floor(maxiumPosterWidth * 0.5);
|
||||
}
|
||||
|
||||
function calculateRowHeight(posterHeight, sortKey, isSmallScreen, overviewOptions) {
|
||||
const {
|
||||
detailedProgressBar
|
||||
} = overviewOptions;
|
||||
|
||||
const heights = [
|
||||
posterHeight,
|
||||
detailedProgressBar ? detailedProgressBarHeight : progressBarHeight,
|
||||
isSmallScreen ? columnPaddingSmallScreen : columnPadding
|
||||
];
|
||||
|
||||
return heights.reduce((acc, height) => acc + height, 0);
|
||||
}
|
||||
|
||||
function calculatePosterHeight(posterWidth) {
|
||||
return posterWidth;
|
||||
}
|
||||
|
||||
class ArtistIndexOverviews extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
width: 0,
|
||||
columnCount: 1,
|
||||
posterWidth: 238,
|
||||
posterHeight: 238,
|
||||
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {})
|
||||
};
|
||||
|
||||
this._isInitialized = false;
|
||||
this._grid = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._contentBodyNode = ReactDOM.findDOMNode(this.props.contentBody);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
items,
|
||||
filterKey,
|
||||
filterValue,
|
||||
sortKey,
|
||||
sortDirection,
|
||||
overviewOptions
|
||||
} = this.props;
|
||||
|
||||
const itemsChanged = hasDifferentItems(prevProps.items, items);
|
||||
const overviewOptionsChanged = !_.isMatch(prevProps.overviewOptions, overviewOptions);
|
||||
|
||||
if (
|
||||
prevProps.sortKey !== sortKey ||
|
||||
prevProps.overviewOptions !== overviewOptions ||
|
||||
itemsChanged
|
||||
) {
|
||||
this.calculateGrid();
|
||||
}
|
||||
|
||||
if (
|
||||
prevProps.filterKey !== filterKey ||
|
||||
prevProps.filterValue !== filterValue ||
|
||||
prevProps.sortKey !== sortKey ||
|
||||
prevProps.sortDirection !== sortDirection ||
|
||||
itemsChanged ||
|
||||
overviewOptionsChanged
|
||||
) {
|
||||
this._grid.recomputeGridSize();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
scrollToFirstCharacter(character) {
|
||||
const items = this.props.items;
|
||||
const {
|
||||
rowHeight
|
||||
} = this.state;
|
||||
|
||||
const index = _.findIndex(items, (item) => {
|
||||
const firstCharacter = item.sortTitle.charAt(0);
|
||||
|
||||
if (character === '#') {
|
||||
return !isNaN(firstCharacter);
|
||||
}
|
||||
|
||||
return firstCharacter === character;
|
||||
});
|
||||
|
||||
if (index != null) {
|
||||
const scrollTop = rowHeight * index;
|
||||
|
||||
this.props.onScroll({ scrollTop });
|
||||
}
|
||||
}
|
||||
|
||||
setGridRef = (ref) => {
|
||||
this._grid = ref;
|
||||
}
|
||||
|
||||
calculateGrid = (width = this.state.width, isSmallScreen) => {
|
||||
const {
|
||||
sortKey,
|
||||
overviewOptions
|
||||
} = this.props;
|
||||
|
||||
const posterWidth = calculatePosterWidth(overviewOptions.size, isSmallScreen);
|
||||
const posterHeight = calculatePosterHeight(posterWidth);
|
||||
const rowHeight = calculateRowHeight(posterHeight, sortKey, isSmallScreen, overviewOptions);
|
||||
|
||||
this.setState({
|
||||
width,
|
||||
posterWidth,
|
||||
posterHeight,
|
||||
rowHeight
|
||||
});
|
||||
}
|
||||
|
||||
cellRenderer = ({ key, rowIndex, style }) => {
|
||||
const {
|
||||
items,
|
||||
sortKey,
|
||||
overviewOptions,
|
||||
showRelativeDates,
|
||||
shortDateFormat,
|
||||
timeFormat,
|
||||
isSmallScreen
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
posterWidth,
|
||||
posterHeight,
|
||||
rowHeight
|
||||
} = this.state;
|
||||
|
||||
const artist = items[rowIndex];
|
||||
|
||||
if (!artist) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ArtistIndexItemConnector
|
||||
key={key}
|
||||
component={ArtistIndexOverview}
|
||||
sortKey={sortKey}
|
||||
posterWidth={posterWidth}
|
||||
posterHeight={posterHeight}
|
||||
rowHeight={rowHeight}
|
||||
overviewOptions={overviewOptions}
|
||||
showRelativeDates={showRelativeDates}
|
||||
shortDateFormat={shortDateFormat}
|
||||
timeFormat={timeFormat}
|
||||
isSmallScreen={isSmallScreen}
|
||||
style={style}
|
||||
{...artist}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onMeasure = ({ width }) => {
|
||||
this.calculateGrid(width, this.props.isSmallScreen);
|
||||
}
|
||||
|
||||
onSectionRendered = () => {
|
||||
if (!this._isInitialized && this._contentBodyNode) {
|
||||
this.props.onRender();
|
||||
this._isInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
items,
|
||||
scrollTop,
|
||||
isSmallScreen,
|
||||
onScroll
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
width,
|
||||
rowHeight
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<Measure onMeasure={this.onMeasure}>
|
||||
<WindowScroller
|
||||
scrollElement={isSmallScreen ? null : this._contentBodyNode}
|
||||
onScroll={onScroll}
|
||||
>
|
||||
{({ height, isScrolling }) => {
|
||||
return (
|
||||
<Grid
|
||||
ref={this.setGridRef}
|
||||
className={styles.grid}
|
||||
autoHeight={true}
|
||||
height={height}
|
||||
columnCount={1}
|
||||
columnWidth={width}
|
||||
rowCount={items.length}
|
||||
rowHeight={rowHeight}
|
||||
width={width}
|
||||
scrollTop={scrollTop}
|
||||
overscanRowCount={2}
|
||||
cellRenderer={this.cellRenderer}
|
||||
onSectionRendered={this.onSectionRendered}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
</WindowScroller>
|
||||
</Measure>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ArtistIndexOverviews.propTypes = {
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
filterKey: PropTypes.string,
|
||||
filterValue: PropTypes.oneOfType([PropTypes.bool, PropTypes.number, PropTypes.string]),
|
||||
sortKey: PropTypes.string,
|
||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
||||
overviewOptions: PropTypes.object.isRequired,
|
||||
scrollTop: PropTypes.number.isRequired,
|
||||
contentBody: PropTypes.object.isRequired,
|
||||
showRelativeDates: PropTypes.bool.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
onRender: PropTypes.func.isRequired,
|
||||
onScroll: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default ArtistIndexOverviews;
|
@ -0,0 +1,33 @@
|
||||
import { createSelector } from 'reselect';
|
||||
import connectSection from 'Store/connectSection';
|
||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import ArtistIndexOverviews from './ArtistIndexOverviews';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.artistIndex.overviewOptions,
|
||||
createClientSideCollectionSelector(),
|
||||
createUISettingsSelector(),
|
||||
createDimensionsSelector(),
|
||||
(overviewOptions, artist, uiSettings, dimensions) => {
|
||||
return {
|
||||
overviewOptions,
|
||||
showRelativeDates: uiSettings.showRelativeDates,
|
||||
shortDateFormat: uiSettings.shortDateFormat,
|
||||
timeFormat: uiSettings.timeFormat,
|
||||
isSmallScreen: dimensions.isSmallScreen,
|
||||
...artist
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default connectSection(
|
||||
createMapStateToProps,
|
||||
undefined,
|
||||
undefined,
|
||||
{ withRef: true },
|
||||
{ section: 'artist', uiSection: 'artistIndex' }
|
||||
)(ArtistIndexOverviews);
|
@ -0,0 +1,25 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import ArtistIndexOverviewOptionsModalContentConnector from './ArtistIndexOverviewOptionsModalContentConnector';
|
||||
|
||||
function ArtistIndexOverviewOptionsModal({ isOpen, onModalClose, ...otherProps }) {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<ArtistIndexOverviewOptionsModalContentConnector
|
||||
{...otherProps}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
ArtistIndexOverviewOptionsModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default ArtistIndexOverviewOptionsModal;
|
@ -0,0 +1,247 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
import Button from 'Components/Link/Button';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
|
||||
const posterSizeOptions = [
|
||||
{ key: 'small', value: 'Small' },
|
||||
{ key: 'medium', value: 'Medium' },
|
||||
{ key: 'large', value: 'Large' }
|
||||
];
|
||||
|
||||
class ArtistIndexOverviewOptionsModalContent extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
detailedProgressBar: props.detailedProgressBar,
|
||||
size: props.size,
|
||||
showQualityProfile: props.showQualityProfile,
|
||||
showPreviousAiring: props.showPreviousAiring,
|
||||
showAdded: props.showAdded,
|
||||
showAlbumCount: props.showAlbumCount,
|
||||
showPath: props.showPath,
|
||||
showSizeOnDisk: props.showSizeOnDisk
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
detailedProgressBar,
|
||||
size,
|
||||
showQualityProfile,
|
||||
showPreviousAiring,
|
||||
showAdded,
|
||||
showAlbumCount,
|
||||
showPath,
|
||||
showSizeOnDisk
|
||||
} = this.props;
|
||||
|
||||
const state = {};
|
||||
|
||||
if (detailedProgressBar !== prevProps.detailedProgressBar) {
|
||||
state.detailedProgressBar = detailedProgressBar;
|
||||
}
|
||||
|
||||
if (size !== prevProps.size) {
|
||||
state.size = size;
|
||||
}
|
||||
|
||||
if (showQualityProfile !== prevProps.showQualityProfile) {
|
||||
state.showQualityProfile = showQualityProfile;
|
||||
}
|
||||
|
||||
if (showPreviousAiring !== prevProps.showPreviousAiring) {
|
||||
state.showPreviousAiring = showPreviousAiring;
|
||||
}
|
||||
|
||||
if (showAdded !== prevProps.showAdded) {
|
||||
state.showAdded = showAdded;
|
||||
}
|
||||
|
||||
if (showAlbumCount !== prevProps.showAlbumCount) {
|
||||
state.showAlbumCount = showAlbumCount;
|
||||
}
|
||||
|
||||
if (showPath !== prevProps.showPath) {
|
||||
state.showPath = showPath;
|
||||
}
|
||||
|
||||
if (showSizeOnDisk !== prevProps.showSizeOnDisk) {
|
||||
state.showSizeOnDisk = showSizeOnDisk;
|
||||
}
|
||||
|
||||
if (!_.isEmpty(state)) {
|
||||
this.setState(state);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onChangeOverviewOption = ({ name, value }) => {
|
||||
this.setState({
|
||||
[name]: value
|
||||
}, () => {
|
||||
this.props.onChangeOverviewOption({ [name]: value });
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
onModalClose
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
detailedProgressBar,
|
||||
size,
|
||||
showQualityProfile,
|
||||
showPreviousAiring,
|
||||
showAdded,
|
||||
showAlbumCount,
|
||||
showPath,
|
||||
showSizeOnDisk
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
Overview Options
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<Form>
|
||||
<FormGroup>
|
||||
<FormLabel>Poster Size</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name="size"
|
||||
value={size}
|
||||
values={posterSizeOptions}
|
||||
onChange={this.onChangeOverviewOption}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Detailed Progress Bar</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="detailedProgressBar"
|
||||
value={detailedProgressBar}
|
||||
helpText="Show text on progess bar"
|
||||
onChange={this.onChangeOverviewOption}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Show Quality Profile</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="showQualityProfile"
|
||||
value={showQualityProfile}
|
||||
onChange={this.onChangeOverviewOption}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Show Previous Airing</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="showPreviousAiring"
|
||||
value={showPreviousAiring}
|
||||
onChange={this.onChangeOverviewOption}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Show Date Added</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="showAdded"
|
||||
value={showAdded}
|
||||
onChange={this.onChangeOverviewOption}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Show Season Count</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="showAlbumCount"
|
||||
value={showAlbumCount}
|
||||
onChange={this.onChangeOverviewOption}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Show Path</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="showPath"
|
||||
value={showPath}
|
||||
onChange={this.onChangeOverviewOption}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Show Size on Disk</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="showSizeOnDisk"
|
||||
value={showSizeOnDisk}
|
||||
onChange={this.onChangeOverviewOption}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button
|
||||
onPress={onModalClose}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ArtistIndexOverviewOptionsModalContent.propTypes = {
|
||||
size: PropTypes.string.isRequired,
|
||||
showQualityProfile: PropTypes.bool.isRequired,
|
||||
showPreviousAiring: PropTypes.bool.isRequired,
|
||||
showAdded: PropTypes.bool.isRequired,
|
||||
showAlbumCount: PropTypes.bool.isRequired,
|
||||
showPath: PropTypes.bool.isRequired,
|
||||
showSizeOnDisk: PropTypes.bool.isRequired,
|
||||
detailedProgressBar: PropTypes.bool.isRequired,
|
||||
onChangeOverviewOption: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default ArtistIndexOverviewOptionsModalContent;
|
@ -0,0 +1,23 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { setArtistOverviewOption } from 'Store/Actions/artistIndexActions';
|
||||
import ArtistIndexOverviewOptionsModalContent from './ArtistIndexOverviewOptionsModalContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.artistIndex,
|
||||
(artistIndex) => {
|
||||
return artistIndex.overviewOptions;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
onChangeOverviewOption(payload) {
|
||||
dispatch(setArtistOverviewOption(payload));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(createMapStateToProps, createMapDispatchToProps)(ArtistIndexOverviewOptionsModalContent);
|
@ -1,14 +0,0 @@
|
||||
.progress {
|
||||
composes: container from 'Components/ProgressBar.css';
|
||||
|
||||
border-radius: 0;
|
||||
background-color: #5b5b5b;
|
||||
color: $white;
|
||||
transition: width 200ms ease;
|
||||
}
|
||||
|
||||
.progressBar {
|
||||
composes: progressBar from 'Components/ProgressBar.css';
|
||||
|
||||
transition: width 200ms ease;
|
||||
}
|
Loading…
Reference in new issue