mirror of https://github.com/lidarr/Lidarr
[UI Work] Add Overview Artist Index View
This commit is contained in:
parent
f6fc78d927
commit
566ac1a9d3
|
@ -1,4 +1,4 @@
|
|||
import _ from 'lodash';
|
||||
/* eslint max-params: 0 */
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createArtistSelector from 'Store/Selectors/createArtistSelector';
|
||||
|
|
|
@ -163,6 +163,7 @@ class ArtistDetails extends Component {
|
|||
foreignArtistId,
|
||||
artistName,
|
||||
ratings,
|
||||
path,
|
||||
sizeOnDisk,
|
||||
trackFileCount,
|
||||
qualityProfileId,
|
||||
|
@ -343,6 +344,21 @@ class ArtistDetails extends Component {
|
|||
</div>
|
||||
|
||||
<div className={styles.detailsLabels}>
|
||||
<Label
|
||||
className={styles.detailsLabel}
|
||||
title={trackFilesCountMessage}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
<Icon
|
||||
name={icons.FOLDER}
|
||||
size={17}
|
||||
/>
|
||||
|
||||
<span className={styles.sizeOnDisk}>
|
||||
{path}
|
||||
</span>
|
||||
</Label>
|
||||
|
||||
<Label
|
||||
className={styles.detailsLabel}
|
||||
title={trackFilesCountMessage}
|
||||
|
@ -547,6 +563,7 @@ ArtistDetails.propTypes = {
|
|||
foreignArtistId: PropTypes.string.isRequired,
|
||||
artistName: PropTypes.string.isRequired,
|
||||
ratings: PropTypes.object.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
sizeOnDisk: PropTypes.number.isRequired,
|
||||
trackFileCount: PropTypes.number,
|
||||
qualityProfileId: PropTypes.number.isRequired,
|
||||
|
|
|
@ -17,6 +17,8 @@ import ArtistIndexPosterOptionsModal from './Posters/Options/ArtistIndexPosterOp
|
|||
import ArtistIndexPostersConnector from './Posters/ArtistIndexPostersConnector';
|
||||
import ArtistIndexBannerOptionsModal from './Banners/Options/ArtistIndexBannerOptionsModal';
|
||||
import ArtistIndexBannersConnector from './Banners/ArtistIndexBannersConnector';
|
||||
import ArtistIndexOverviewOptionsModal from './Overview/Options/ArtistIndexOverviewOptionsModal';
|
||||
import ArtistIndexOverviewsConnector from './Overview/ArtistIndexOverviewsConnector';
|
||||
import ArtistIndexFooter from './ArtistIndexFooter';
|
||||
import ArtistIndexFilterMenu from './Menus/ArtistIndexFilterMenu';
|
||||
import ArtistIndexSortMenu from './Menus/ArtistIndexSortMenu';
|
||||
|
@ -32,6 +34,10 @@ function getViewComponent(view) {
|
|||
return ArtistIndexBannersConnector;
|
||||
}
|
||||
|
||||
if (view === 'overview') {
|
||||
return ArtistIndexOverviewsConnector;
|
||||
}
|
||||
|
||||
return ArtistIndexTableConnector;
|
||||
}
|
||||
|
||||
|
@ -50,6 +56,7 @@ class ArtistIndex extends Component {
|
|||
jumpBarItems: [],
|
||||
isPosterOptionsModalOpen: false,
|
||||
isBannerOptionsModalOpen: false,
|
||||
isOverviewOptionsModalOpen: false,
|
||||
isRendered: false
|
||||
};
|
||||
}
|
||||
|
@ -137,6 +144,14 @@ class ArtistIndex extends Component {
|
|||
this.setState({ isBannerOptionsModalOpen: false });
|
||||
}
|
||||
|
||||
onOverviewOptionsPress = () => {
|
||||
this.setState({ isOverviewOptionsModalOpen: true });
|
||||
}
|
||||
|
||||
onOverviewOptionsModalClose = () => {
|
||||
this.setState({ isOverviewOptionsModalOpen: false });
|
||||
}
|
||||
|
||||
onJumpBarItemPress = (item) => {
|
||||
const viewComponent = this._viewComponent.getWrappedInstance();
|
||||
viewComponent.scrollToFirstCharacter(item);
|
||||
|
@ -193,6 +208,7 @@ class ArtistIndex extends Component {
|
|||
jumpBarItems,
|
||||
isPosterOptionsModalOpen,
|
||||
isBannerOptionsModalOpen,
|
||||
isOverviewOptionsModalOpen,
|
||||
isRendered
|
||||
} = this.state;
|
||||
|
||||
|
@ -234,11 +250,6 @@ class ArtistIndex extends Component {
|
|||
/>
|
||||
}
|
||||
|
||||
{
|
||||
view === 'posters' &&
|
||||
<PageToolbarSeparator />
|
||||
}
|
||||
|
||||
{
|
||||
view === 'banners' &&
|
||||
<PageToolbarButton
|
||||
|
@ -249,7 +260,17 @@ class ArtistIndex extends Component {
|
|||
}
|
||||
|
||||
{
|
||||
view === 'banners' &&
|
||||
view === 'overview' &&
|
||||
<PageToolbarButton
|
||||
label="Options"
|
||||
iconName={icons.OVERVIEW}
|
||||
onPress={this.onOverviewOptionsPress}
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
(view === 'posters' || view === 'banners' || view === 'overview') &&
|
||||
|
||||
<PageToolbarSeparator />
|
||||
}
|
||||
|
||||
|
@ -330,6 +351,13 @@ class ArtistIndex extends Component {
|
|||
<ArtistIndexBannerOptionsModal
|
||||
isOpen={isBannerOptionsModalOpen}
|
||||
onModalClose={this.onBannerOptionsModalClose}
|
||||
|
||||
/>
|
||||
|
||||
<ArtistIndexOverviewOptionsModal
|
||||
isOpen={isOverviewOptionsModalOpen}
|
||||
onModalClose={this.onOverviewOptionsModalClose}
|
||||
|
||||
/>
|
||||
</PageContent>
|
||||
);
|
||||
|
|
|
@ -9,7 +9,7 @@ import Link from 'Components/Link/Link';
|
|||
import ArtistBanner from 'Artist/ArtistBanner';
|
||||
import EditArtistModalConnector from 'Artist/Edit/EditArtistModalConnector';
|
||||
import DeleteArtistModal from 'Artist/Delete/DeleteArtistModal';
|
||||
import ArtistIndexBannerProgressBar from './ArtistIndexBannerProgressBar';
|
||||
import ArtistIndexProgressBar from 'Artist/Index/ProgressBar/ArtistIndexProgressBar';
|
||||
import ArtistIndexBannerInfo from './ArtistIndexBannerInfo';
|
||||
import styles from './ArtistIndexBanner.css';
|
||||
|
||||
|
@ -135,12 +135,12 @@ class ArtistIndexBanner extends Component {
|
|||
</Link>
|
||||
</div>
|
||||
|
||||
<ArtistIndexBannerProgressBar
|
||||
<ArtistIndexProgressBar
|
||||
monitored={monitored}
|
||||
status={status}
|
||||
trackCount={trackCount}
|
||||
trackFileCount={trackFileCount}
|
||||
bannerWidth={bannerWidth}
|
||||
posterWidth={bannerWidth}
|
||||
detailedProgressBar={detailedProgressBar}
|
||||
/>
|
||||
|
||||
|
@ -174,6 +174,7 @@ class ArtistIndexBanner extends Component {
|
|||
|
||||
<ArtistIndexBannerInfo
|
||||
qualityProfile={qualityProfile}
|
||||
showQualityProfile={showQualityProfile}
|
||||
showRelativeDates={showRelativeDates}
|
||||
shortDateFormat={shortDateFormat}
|
||||
timeFormat={timeFormat}
|
||||
|
|
|
@ -7,6 +7,7 @@ import styles from './ArtistIndexBannerInfo.css';
|
|||
function ArtistIndexBannerInfo(props) {
|
||||
const {
|
||||
qualityProfile,
|
||||
showQualityProfile,
|
||||
previousAiring,
|
||||
added,
|
||||
albumCount,
|
||||
|
@ -18,7 +19,7 @@ function ArtistIndexBannerInfo(props) {
|
|||
timeFormat
|
||||
} = props;
|
||||
|
||||
if (sortKey === 'qualityProfileId') {
|
||||
if (sortKey === 'qualityProfileId' && !showQualityProfile) {
|
||||
return (
|
||||
<div className={styles.info}>
|
||||
{qualityProfile.name}
|
||||
|
@ -99,6 +100,7 @@ function ArtistIndexBannerInfo(props) {
|
|||
|
||||
ArtistIndexBannerInfo.propTypes = {
|
||||
qualityProfile: PropTypes.object.isRequired,
|
||||
showQualityProfile: PropTypes.bool.isRequired,
|
||||
previousAiring: PropTypes.string,
|
||||
added: PropTypes.string,
|
||||
albumCount: PropTypes.number.isRequired,
|
||||
|
|
|
@ -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;
|
|
@ -12,8 +12,8 @@ import ArtistIndexBanner from './ArtistIndexBanner';
|
|||
import styles from './ArtistIndexBanners.css';
|
||||
|
||||
// container dimensions
|
||||
const columnPadding = 20;
|
||||
const columnPaddingSmallScreen = 10;
|
||||
const columnPadding = parseInt(dimensions.artistIndexColumnPadding);
|
||||
const columnPaddingSmallScreen = parseInt(dimensions.artistIndexColumnPaddingSmallScreen);
|
||||
const progressBarHeight = parseInt(dimensions.progressBarSmallHeight);
|
||||
const detailedProgressBarHeight = parseInt(dimensions.progressBarMediumHeight);
|
||||
|
||||
|
@ -178,7 +178,7 @@ class ArtistIndexBanners extends Component {
|
|||
} = this.props;
|
||||
|
||||
const padding = isSmallScreen ? columnPaddingSmallScreen : columnPadding;
|
||||
const columnWidth = calculateColumnWidth(width, this.props.bannerOptions.size);
|
||||
const columnWidth = calculateColumnWidth(width, bannerOptions.size, isSmallScreen);
|
||||
const columnCount = Math.max(Math.floor(width / columnWidth), 1);
|
||||
const bannerWidth = columnWidth - padding;
|
||||
const bannerHeight = calculateHeight(bannerWidth);
|
||||
|
@ -300,7 +300,7 @@ class ArtistIndexBanners extends Component {
|
|||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
</WindowScroller>
|
||||
</Measure>
|
||||
);
|
||||
|
|
|
@ -37,6 +37,14 @@ function ArtistIndexViewMenu(props) {
|
|||
>
|
||||
Banners
|
||||
</ViewMenuItem>
|
||||
|
||||
<ViewMenuItem
|
||||
name="overview"
|
||||
selectedView={view}
|
||||
onPress={onViewSelect}
|
||||
>
|
||||
Overview
|
||||
</ViewMenuItem>
|
||||
</MenuContent>
|
||||
</ViewMenu>
|
||||
);
|
||||
|
|
|
@ -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);
|
|
@ -9,7 +9,7 @@ import Link from 'Components/Link/Link';
|
|||
import ArtistPoster from 'Artist/ArtistPoster';
|
||||
import EditArtistModalConnector from 'Artist/Edit/EditArtistModalConnector';
|
||||
import DeleteArtistModal from 'Artist/Delete/DeleteArtistModal';
|
||||
import ArtistIndexPosterProgressBar from './ArtistIndexPosterProgressBar';
|
||||
import ArtistIndexProgressBar from 'Artist/Index/ProgressBar/ArtistIndexProgressBar';
|
||||
import ArtistIndexPosterInfo from './ArtistIndexPosterInfo';
|
||||
import styles from './ArtistIndexPoster.css';
|
||||
|
||||
|
@ -135,7 +135,7 @@ class ArtistIndexPoster extends Component {
|
|||
</Link>
|
||||
</div>
|
||||
|
||||
<ArtistIndexPosterProgressBar
|
||||
<ArtistIndexProgressBar
|
||||
monitored={monitored}
|
||||
status={status}
|
||||
trackCount={trackCount}
|
||||
|
@ -174,6 +174,7 @@ class ArtistIndexPoster extends Component {
|
|||
|
||||
<ArtistIndexPosterInfo
|
||||
qualityProfile={qualityProfile}
|
||||
showQualityProfile={showQualityProfile}
|
||||
showRelativeDates={showRelativeDates}
|
||||
shortDateFormat={shortDateFormat}
|
||||
timeFormat={timeFormat}
|
||||
|
|
|
@ -7,6 +7,7 @@ import styles from './ArtistIndexPosterInfo.css';
|
|||
function ArtistIndexPosterInfo(props) {
|
||||
const {
|
||||
qualityProfile,
|
||||
showQualityProfile,
|
||||
previousAiring,
|
||||
added,
|
||||
albumCount,
|
||||
|
@ -18,7 +19,7 @@ function ArtistIndexPosterInfo(props) {
|
|||
timeFormat
|
||||
} = props;
|
||||
|
||||
if (sortKey === 'qualityProfileId') {
|
||||
if (sortKey === 'qualityProfileId' && !showQualityProfile) {
|
||||
return (
|
||||
<div className={styles.info}>
|
||||
{qualityProfile.name}
|
||||
|
@ -99,6 +100,7 @@ function ArtistIndexPosterInfo(props) {
|
|||
|
||||
ArtistIndexPosterInfo.propTypes = {
|
||||
qualityProfile: PropTypes.object.isRequired,
|
||||
showQualityProfile: PropTypes.bool.isRequired,
|
||||
previousAiring: PropTypes.string,
|
||||
added: PropTypes.string,
|
||||
albumCount: PropTypes.number.isRequired,
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -12,8 +12,8 @@ import ArtistIndexPoster from './ArtistIndexPoster';
|
|||
import styles from './ArtistIndexPosters.css';
|
||||
|
||||
// Poster container dimensions
|
||||
const columnPadding = 20;
|
||||
const columnPaddingSmallScreen = 10;
|
||||
const columnPadding = parseInt(dimensions.artistIndexColumnPadding);
|
||||
const columnPaddingSmallScreen = parseInt(dimensions.artistIndexColumnPaddingSmallScreen);
|
||||
const progressBarHeight = parseInt(dimensions.progressBarSmallHeight);
|
||||
const detailedProgressBarHeight = parseInt(dimensions.progressBarMediumHeight);
|
||||
|
||||
|
@ -178,7 +178,7 @@ class ArtistIndexPosters extends Component {
|
|||
} = this.props;
|
||||
|
||||
const padding = isSmallScreen ? columnPaddingSmallScreen : columnPadding;
|
||||
const columnWidth = calculateColumnWidth(width, this.props.posterOptions.size);
|
||||
const columnWidth = calculateColumnWidth(width, posterOptions.size, isSmallScreen);
|
||||
const columnCount = Math.max(Math.floor(width / columnWidth), 1);
|
||||
const posterWidth = columnWidth - padding;
|
||||
const posterHeight = calculatePosterHeight(posterWidth);
|
||||
|
|
|
@ -3,9 +3,9 @@ import React from 'react';
|
|||
import getProgressBarKind from 'Utilities/Series/getProgressBarKind';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import ProgressBar from 'Components/ProgressBar';
|
||||
import styles from './ArtistIndexPosterProgressBar.css';
|
||||
import styles from './ArtistIndexProgressBar.css';
|
||||
|
||||
function ArtistIndexPosterProgressBar(props) {
|
||||
function ArtistIndexProgressBar(props) {
|
||||
const {
|
||||
monitored,
|
||||
status,
|
||||
|
@ -33,7 +33,7 @@ function ArtistIndexPosterProgressBar(props) {
|
|||
);
|
||||
}
|
||||
|
||||
ArtistIndexPosterProgressBar.propTypes = {
|
||||
ArtistIndexProgressBar.propTypes = {
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
trackCount: PropTypes.number.isRequired,
|
||||
|
@ -42,4 +42,4 @@ ArtistIndexPosterProgressBar.propTypes = {
|
|||
detailedProgressBar: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
export default ArtistIndexPosterProgressBar;
|
||||
export default ArtistIndexProgressBar;
|
|
@ -35,11 +35,10 @@ class CalendarPage extends Component {
|
|||
// Listeners
|
||||
|
||||
onMeasure = ({ width }) => {
|
||||
this.setState({ width }, () => {
|
||||
const days = Math.max(3, Math.min(7, Math.floor(width / MINIMUM_DAY_WIDTH)));
|
||||
console.log(`${width} || ${days}`);
|
||||
this.props.onDaysCountChange(days);
|
||||
});
|
||||
this.setState({ width });
|
||||
const days = Math.max(3, Math.min(7, Math.floor(width / MINIMUM_DAY_WIDTH)));
|
||||
|
||||
this.props.onDaysCountChange(days);
|
||||
}
|
||||
|
||||
onFilterMenuItemPress = (filterKey, unmonitored) => {
|
||||
|
@ -109,7 +108,11 @@ class CalendarPage extends Component {
|
|||
whitelist={['width']}
|
||||
onMeasure={this.onMeasure}
|
||||
>
|
||||
<CalendarConnector />
|
||||
{
|
||||
this.state.width > 0 ?
|
||||
<CalendarConnector /> :
|
||||
<div />
|
||||
}
|
||||
</Measure>
|
||||
|
||||
<Legend colorImpairedMode={colorImpairedMode} />
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.description {
|
||||
line-height: 1.528571429;
|
||||
line-height: $lineHeight;
|
||||
}
|
||||
|
||||
.description {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.title {
|
||||
line-height: 1.528571429;
|
||||
line-height: $lineHeight;
|
||||
}
|
||||
|
||||
.title {
|
||||
|
|
|
@ -75,7 +75,6 @@ class PathInput extends Component {
|
|||
|
||||
onInputBlur = () => {
|
||||
this.props.onClearPaths();
|
||||
this.props.onFetchPaths('');
|
||||
}
|
||||
|
||||
onSuggestionsFetchRequested = ({ value }) => {
|
||||
|
@ -85,7 +84,6 @@ class PathInput extends Component {
|
|||
onSuggestionsClearRequested = () => {
|
||||
// Required because props aren't always rendered, but no-op
|
||||
// because we don't want to reset the paths after a path is selected.
|
||||
// `onInputBlur` will handle clearing when the user leaves the input.
|
||||
}
|
||||
|
||||
onSuggestionSelected = (event, { suggestionValue }) => {
|
||||
|
|
|
@ -69,7 +69,7 @@ class SignalRConnector extends Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
console.log('starting signalR');
|
||||
console.log('Starting signalR');
|
||||
|
||||
this.signalRconnection = $.connection('/signalr', { apiKey: window.Sonarr.apiKey });
|
||||
|
||||
|
@ -281,7 +281,7 @@ class SignalRConnector extends Component {
|
|||
|
||||
onStateChanged = (change) => {
|
||||
const state = getState(change.newState);
|
||||
console.log(`SignalR: [${state}]`);
|
||||
console.log(`SignalR: ${state}`);
|
||||
|
||||
if (state === 'connected') {
|
||||
// Repopulate the page (if a repopulator is set) to ensure things
|
||||
|
|
|
@ -13,6 +13,7 @@ export const CARET_DOWN = 'fa fa-caret-down';
|
|||
export const CHECK = 'fa fa-check';
|
||||
export const CHECK_INDETERMINATE = 'fa fa-minus';
|
||||
export const CHECK_CIRCLE = 'fa fa-check-circle';
|
||||
export const CIRCLE = 'fa fa-circle';
|
||||
export const CIRCLE_OUTLINE = 'fa fa-circle-o';
|
||||
export const CLEAR = 'fa fa-trash';
|
||||
export const CLIPBOARD = 'fa fa-clipboard';
|
||||
|
@ -48,6 +49,7 @@ export const NAVBAR_COLLAPSE = 'fa fa-bars';
|
|||
export const NOT_AIRED = 'fa fa-clock-o';
|
||||
export const ORGANIZE = 'fa fa-sitemap';
|
||||
export const OVERFLOW = 'fa fa-ellipsis-h';
|
||||
export const OVERVIEW = 'fa fa-th-list';
|
||||
export const PAGE_FIRST = 'fa fa-fast-backward';
|
||||
export const PAGE_PREVIOUS = 'fa fa-backward';
|
||||
export const PAGE_NEXT = 'fa fa-forward';
|
||||
|
|
|
@ -33,24 +33,24 @@ class UISettings extends Component {
|
|||
];
|
||||
|
||||
const weekColumnOptions = [
|
||||
{ key: 'ddd M/D', value: 'Tue 3/5' },
|
||||
{ key: 'ddd MM/DD', value: 'Tue 03/05' },
|
||||
{ key: 'ddd D/M', value: 'Tue 5/3' },
|
||||
{ key: 'ddd DD/MM', value: 'Tue 05/03' }
|
||||
{ key: 'ddd M/D', value: 'Tue 3/25' },
|
||||
{ key: 'ddd MM/DD', value: 'Tue 03/25' },
|
||||
{ key: 'ddd D/M', value: 'Tue 25/3' },
|
||||
{ key: 'ddd DD/MM', value: 'Tue 25/03' }
|
||||
];
|
||||
|
||||
const shortDateFormatOptions = [
|
||||
{ key: 'MMM D YYYY', value: 'Mar 5 2014' },
|
||||
{ key: 'DD MMM YYYY', value: '5 Mar 2014' },
|
||||
{ key: 'MM/D/YYYY', value: '03/5/2014' },
|
||||
{ key: 'MM/DD/YYYY', value: '03/05/2014' },
|
||||
{ key: 'DD/MM/YYYY', value: '05/03/2014' },
|
||||
{ key: 'YYYY-MM-DD', value: '2014-03-05' }
|
||||
{ key: 'MMM D YYYY', value: 'Mar 25 2014' },
|
||||
{ key: 'DD MMM YYYY', value: '25 Mar 2014' },
|
||||
{ key: 'MM/D/YYYY', value: '03/25/2014' },
|
||||
{ key: 'MM/DD/YYYY', value: '03/25/2014' },
|
||||
{ key: 'DD/MM/YYYY', value: '25/03/2014' },
|
||||
{ key: 'YYYY-MM-DD', value: '2014-03-25' }
|
||||
];
|
||||
|
||||
const longDateFormatOptions = [
|
||||
{ key: 'dddd, MMMM D YYYY', value: 'Tuesday, March 5, 2014' },
|
||||
{ key: 'dddd, D MMMM YYYY', value: 'Tuesday, 5 March, 2014' }
|
||||
{ key: 'dddd, MMMM D YYYY', value: 'Tuesday, March 25, 2014' },
|
||||
{ key: 'dddd, D MMMM YYYY', value: 'Tuesday, 25 March, 2014' }
|
||||
];
|
||||
|
||||
const timeFormatOptions = [
|
||||
|
|
|
@ -55,6 +55,7 @@ export const SET_ARTIST_VIEW = 'SET_ARTIST_VIEW';
|
|||
export const SET_ARTIST_TABLE_OPTION = 'SET_ARTIST_TABLE_OPTION';
|
||||
export const SET_ARTIST_POSTER_OPTION = 'SET_ARTIST_POSTER_OPTION';
|
||||
export const SET_ARTIST_BANNER_OPTION = 'SET_ARTIST_BANNER_OPTION';
|
||||
export const SET_ARTIST_OVERVIEW_OPTION = 'SET_ARTIST_OVERVIEW_OPTION';
|
||||
|
||||
export const TOGGLE_ARTIST_MONITORED = 'TOGGLE_ARTIST_MONITORED';
|
||||
export const TOGGLE_ALBUM_MONITORED = 'TOGGLE_ALBUM_MONITORED';
|
||||
|
|
|
@ -7,3 +7,4 @@ export const setArtistView = createAction(types.SET_ARTIST_VIEW);
|
|||
export const setArtistTableOption = createAction(types.SET_ARTIST_TABLE_OPTION);
|
||||
export const setArtistPosterOption = createAction(types.SET_ARTIST_POSTER_OPTION);
|
||||
export const setArtistBannerOption = createAction(types.SET_ARTIST_BANNER_OPTION);
|
||||
export const setArtistOverviewOption = createAction(types.SET_ARTIST_OVERVIEW_OPTION);
|
||||
|
|
|
@ -176,6 +176,10 @@ const calendarActionHandlers = {
|
|||
|
||||
[types.SET_CALENDAR_DAYS_COUNT]: function(payload) {
|
||||
return function(dispatch, getState) {
|
||||
if (payload.dayCount === getState().calendar.dayCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(set({
|
||||
section,
|
||||
dayCount: payload.dayCount
|
||||
|
@ -184,10 +188,7 @@ const calendarActionHandlers = {
|
|||
const state = getState();
|
||||
const { time, view } = state.calendar;
|
||||
|
||||
dispatch(fetchCalendar({
|
||||
time,
|
||||
view
|
||||
}));
|
||||
dispatch(fetchCalendar({ time, view }));
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -201,10 +202,7 @@ const calendarActionHandlers = {
|
|||
const state = getState();
|
||||
const { time, view } = state.calendar;
|
||||
|
||||
dispatch(fetchCalendar({
|
||||
time,
|
||||
view
|
||||
}));
|
||||
dispatch(fetchCalendar({ time, view }));
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -212,7 +210,9 @@ const calendarActionHandlers = {
|
|||
return function(dispatch, getState) {
|
||||
const state = getState();
|
||||
const view = payload.view;
|
||||
const time = view === calendarViews.FORECAST ? moment() : state.calendar.time;
|
||||
const time = view === calendarViews.FORECAST ?
|
||||
moment() :
|
||||
state.calendar.time;
|
||||
|
||||
dispatch(fetchCalendar({ time, view }));
|
||||
};
|
||||
|
|
|
@ -206,7 +206,10 @@ const queueActionHandlers = {
|
|||
});
|
||||
|
||||
promise.done((data) => {
|
||||
dispatch(fetchQueue());
|
||||
dispatch(batchActions([
|
||||
set({ section, isRemoving: false }),
|
||||
fetchQueue()
|
||||
]));
|
||||
});
|
||||
|
||||
promise.fail((xhr) => {
|
||||
|
|
|
@ -21,14 +21,26 @@ export const defaultState = {
|
|||
detailedProgressBar: false,
|
||||
size: 'large',
|
||||
showTitle: false,
|
||||
showQualityProfile: false
|
||||
showQualityProfile: true
|
||||
},
|
||||
|
||||
bannerOptions: {
|
||||
detailedProgressBar: false,
|
||||
size: 'large',
|
||||
showTitle: false,
|
||||
showQualityProfile: false
|
||||
showQualityProfile: true
|
||||
},
|
||||
|
||||
overviewOptions: {
|
||||
detailedProgressBar: false,
|
||||
size: 'medium',
|
||||
showNetwork: true,
|
||||
showQualityProfile: true,
|
||||
showPreviousAiring: false,
|
||||
showAdded: false,
|
||||
showAlbumCount: true,
|
||||
showPath: false,
|
||||
showSizeOnDisk: false
|
||||
},
|
||||
|
||||
columns: [
|
||||
|
@ -175,7 +187,8 @@ export const persistState = [
|
|||
'artistIndex.view',
|
||||
'artistIndex.columns',
|
||||
'artistIndex.posterOptions',
|
||||
'artistIndex.bannerOptions'
|
||||
'artistIndex.bannerOptions',
|
||||
'artistIndex.overviewOptions'
|
||||
];
|
||||
|
||||
const reducerSection = 'artistIndex';
|
||||
|
@ -215,6 +228,18 @@ const artistIndexReducers = handleActions({
|
|||
...payload
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
[types.SET_ARTIST_OVERVIEW_OPTION]: function(state, { payload }) {
|
||||
const overviewOptions = state.overviewOptions;
|
||||
|
||||
return {
|
||||
...state,
|
||||
overviewOptions: {
|
||||
...overviewOptions,
|
||||
...payload
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}, defaultState);
|
||||
|
|
|
@ -36,8 +36,10 @@ module.exports = {
|
|||
progressBarLargeHeight: '20px',
|
||||
|
||||
// Jump Bar
|
||||
jumpBarItemHeight: '25px'
|
||||
|
||||
// Series
|
||||
jumpBarItemHeight: '25px',
|
||||
|
||||
// Artist
|
||||
artistIndexColumnPadding: '20px',
|
||||
artistIndexColumnPaddingSmallScreen: '10px',
|
||||
artistIndexOverviewInfoRowHeight: '21px'
|
||||
};
|
||||
|
|
|
@ -7,5 +7,7 @@ module.exports = {
|
|||
extraSmallFontSize: '11px',
|
||||
smallFontSize: '12px',
|
||||
defaultFontSize: '14px',
|
||||
largeFontSize: '16px'
|
||||
largeFontSize: '16px',
|
||||
|
||||
lineHeight: '1.528571429'
|
||||
};
|
||||
|
|
|
@ -77,8 +77,8 @@
|
|||
"react-async-script": "0.9.1",
|
||||
"react-autosuggest": "9.3.2",
|
||||
"react-custom-scrollbars": "4.1.2",
|
||||
"react-dnd": "2.5.3",
|
||||
"react-dnd-html5-backend": "2.5.3",
|
||||
"react-dnd": "2.5.4",
|
||||
"react-dnd-html5-backend": "2.5.4",
|
||||
"react-document-title": "2.0.3",
|
||||
"react-dom": "15.6.0",
|
||||
"react-google-recaptcha": "0.9.7",
|
||||
|
@ -92,6 +92,7 @@
|
|||
"react-tabs": "2.1.0",
|
||||
"react-tag-autocomplete": "5.4.1",
|
||||
"react-tether": "0.5.7",
|
||||
"react-truncate": "2.1.5",
|
||||
"react-virtualized": "9.10.1",
|
||||
"redux": "3.7.2",
|
||||
"redux-actions": "2.2.1",
|
||||
|
|
|
@ -81,7 +81,7 @@ namespace Lidarr.Api.V3.Calendar
|
|||
//occurrence.Description = album.Overview;
|
||||
//occurrence.Categories = new List<string>() { album.Series.Network };
|
||||
|
||||
occurrence.Start = new CalDateTime(album.ReleaseDate.Value) { HasTime = false };
|
||||
occurrence.Start = new CalDateTime(album.ReleaseDate.Value.ToLocalTime()) { HasTime = false };
|
||||
|
||||
occurrence.Summary = $"{album.Artist.Name} - {album.Title}";
|
||||
}
|
||||
|
|
24
yarn.lock
24
yarn.lock
|
@ -1921,9 +1921,9 @@ disposables@^1.0.1:
|
|||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/disposables/-/disposables-1.0.1.tgz#064727a25b54f502bd82b89aa2dfb8df9f1b39e3"
|
||||
|
||||
dnd-core@^2.5.3:
|
||||
version "2.5.3"
|
||||
resolved "https://registry.yarnpkg.com/dnd-core/-/dnd-core-2.5.3.tgz#a4163d59a01779860d2edbdadab5183ce321147c"
|
||||
dnd-core@^2.5.4:
|
||||
version "2.5.4"
|
||||
resolved "https://registry.yarnpkg.com/dnd-core/-/dnd-core-2.5.4.tgz#0c70a8dcbb609c0b222e275fcae9fa83e5897397"
|
||||
dependencies:
|
||||
asap "^2.0.6"
|
||||
invariant "^2.0.0"
|
||||
|
@ -5246,18 +5246,18 @@ react-custom-scrollbars@4.1.2:
|
|||
prop-types "^15.5.10"
|
||||
raf "^3.1.0"
|
||||
|
||||
react-dnd-html5-backend@2.5.3:
|
||||
version "2.5.3"
|
||||
resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-2.5.3.tgz#1158112e8129dfe672d4c72222f266dd05321f03"
|
||||
react-dnd-html5-backend@2.5.4:
|
||||
version "2.5.4"
|
||||
resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-2.5.4.tgz#974ad083f67b12d56977a5b171f5ffeb29d78352"
|
||||
dependencies:
|
||||
lodash "^4.2.0"
|
||||
|
||||
react-dnd@2.5.3:
|
||||
version "2.5.3"
|
||||
resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-2.5.3.tgz#ee5357da19d4ebd0e0da0a070a862bbc133e3241"
|
||||
react-dnd@2.5.4:
|
||||
version "2.5.4"
|
||||
resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-2.5.4.tgz#0b6dc5e9d0dfc2909f4f4fe736e5534f3afd1bd9"
|
||||
dependencies:
|
||||
disposables "^1.0.1"
|
||||
dnd-core "^2.5.3"
|
||||
dnd-core "^2.5.4"
|
||||
hoist-non-react-statics "^2.1.0"
|
||||
invariant "^2.1.0"
|
||||
lodash "^4.2.0"
|
||||
|
@ -5382,6 +5382,10 @@ react-themeable@^1.1.0:
|
|||
dependencies:
|
||||
object-assign "^3.0.0"
|
||||
|
||||
react-truncate@2.1.5:
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/react-truncate/-/react-truncate-2.1.5.tgz#c33c1bf7053c2800d4012fefab09a18013019721"
|
||||
|
||||
react-virtualized@9.10.1:
|
||||
version "9.10.1"
|
||||
resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.10.1.tgz#d32365d0edf49debbe25fbfe73b5f55f6d9d8c72"
|
||||
|
|
Loading…
Reference in New Issue