diff --git a/frontend/src/Artist/Details/AlbumRowConnector.js b/frontend/src/Artist/Details/AlbumRowConnector.js index 285598bcb..cff75e50f 100644 --- a/frontend/src/Artist/Details/AlbumRowConnector.js +++ b/frontend/src/Artist/Details/AlbumRowConnector.js @@ -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'; diff --git a/frontend/src/Artist/Details/ArtistDetails.js b/frontend/src/Artist/Details/ArtistDetails.js index 20108c881..fc6657f70 100644 --- a/frontend/src/Artist/Details/ArtistDetails.js +++ b/frontend/src/Artist/Details/ArtistDetails.js @@ -163,6 +163,7 @@ class ArtistDetails extends Component { foreignArtistId, artistName, ratings, + path, sizeOnDisk, trackFileCount, qualityProfileId, @@ -343,6 +344,21 @@ class ArtistDetails extends Component {
+ +
- @@ -174,6 +174,7 @@ class ArtistIndexBanner extends Component { {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, diff --git a/frontend/src/Artist/Index/Banners/ArtistIndexBannerProgressBar.js b/frontend/src/Artist/Index/Banners/ArtistIndexBannerProgressBar.js deleted file mode 100644 index fa9931a02..000000000 --- a/frontend/src/Artist/Index/Banners/ArtistIndexBannerProgressBar.js +++ /dev/null @@ -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 ( - - ); -} - -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; diff --git a/frontend/src/Artist/Index/Banners/ArtistIndexBanners.js b/frontend/src/Artist/Index/Banners/ArtistIndexBanners.js index 89f00d965..986632ab4 100644 --- a/frontend/src/Artist/Index/Banners/ArtistIndexBanners.js +++ b/frontend/src/Artist/Index/Banners/ArtistIndexBanners.js @@ -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 { /> ); } - } + } ); diff --git a/frontend/src/Artist/Index/Menus/ArtistIndexViewMenu.js b/frontend/src/Artist/Index/Menus/ArtistIndexViewMenu.js index a8f0cd3ee..b227a96b6 100644 --- a/frontend/src/Artist/Index/Menus/ArtistIndexViewMenu.js +++ b/frontend/src/Artist/Index/Menus/ArtistIndexViewMenu.js @@ -37,6 +37,14 @@ function ArtistIndexViewMenu(props) { > Banners + + + Overview + ); diff --git a/frontend/src/Artist/Index/Overview/ArtistIndexOverview.css b/frontend/src/Artist/Index/Overview/ArtistIndexOverview.css new file mode 100644 index 000000000..5efdbc27a --- /dev/null +++ b/frontend/src/Artist/Index/Overview/ArtistIndexOverview.css @@ -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; + } +} diff --git a/frontend/src/Artist/Index/Overview/ArtistIndexOverview.js b/frontend/src/Artist/Index/Overview/ArtistIndexOverview.js new file mode 100644 index 000000000..ebab07ce5 --- /dev/null +++ b/frontend/src/Artist/Index/Overview/ArtistIndexOverview.js @@ -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 ( +
+
+
+ { + status === 'ended' && +
+ } + + + + +
+ + +
+ +
+
+ + {artistName} + + +
+ + + +
+
+ +
+ + + {overview} + + + + +
+
+ + + + +
+ ); + } +} + +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; diff --git a/frontend/src/Artist/Index/Overview/ArtistIndexOverviewInfo.css b/frontend/src/Artist/Index/Overview/ArtistIndexOverviewInfo.css new file mode 100644 index 000000000..aa25a676c --- /dev/null +++ b/frontend/src/Artist/Index/Overview/ArtistIndexOverviewInfo.css @@ -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; + } +} diff --git a/frontend/src/Artist/Index/Overview/ArtistIndexOverviewInfo.js b/frontend/src/Artist/Index/Overview/ArtistIndexOverviewInfo.js new file mode 100644 index 000000000..31a114e70 --- /dev/null +++ b/frontend/src/Artist/Index/Overview/ArtistIndexOverviewInfo.js @@ -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 ( +
+ { + !!nextAiring && +
+ + + { + getRelativeDate( + nextAiring, + shortDateFormat, + showRelativeDates, + { + timeFormat, + timeForToday: true + } + ) + } +
+ } + + { + isVisible('qualityProfileId', showQualityProfile, qualityProfile, sortKey) && maxRows > 1 && +
+ + + {qualityProfile.name} +
+ } + + { + isVisible('added', showAdded, added, sortKey) && maxRows > 2 && +
+ + + { + getRelativeDate( + added, + shortDateFormat, + showRelativeDates, + { + timeFormat, + timeForToday: true + } + ) + } +
+ } + + { + isVisible('albumCount', showAlbumCount, albumCount, sortKey) && maxRows > 3 && +
+ + + {albums} +
+ } + + { + isVisible('path', showPath, path, sortKey) && maxRows > 4 && +
+ + + {path} +
+ } + + { + isVisible('sizeOnDisk', showSizeOnDisk, sizeOnDisk, sortKey) && maxRows > 5 && +
+ + + {formatBytes(sizeOnDisk)} +
+ } + +
+ ); +} + +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; diff --git a/frontend/src/Artist/Index/Overview/ArtistIndexOverviews.css b/frontend/src/Artist/Index/Overview/ArtistIndexOverviews.css new file mode 100644 index 000000000..9c6520fb5 --- /dev/null +++ b/frontend/src/Artist/Index/Overview/ArtistIndexOverviews.css @@ -0,0 +1,3 @@ +.grid { + flex: 1 0 auto; +} diff --git a/frontend/src/Artist/Index/Overview/ArtistIndexOverviews.js b/frontend/src/Artist/Index/Overview/ArtistIndexOverviews.js new file mode 100644 index 000000000..801a9d35b --- /dev/null +++ b/frontend/src/Artist/Index/Overview/ArtistIndexOverviews.js @@ -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 ( + + ); + } + + // + // 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 ( + + + {({ height, isScrolling }) => { + return ( + + ); + } + } + + + ); + } +} + +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; diff --git a/frontend/src/Artist/Index/Overview/ArtistIndexOverviewsConnector.js b/frontend/src/Artist/Index/Overview/ArtistIndexOverviewsConnector.js new file mode 100644 index 000000000..a2075416f --- /dev/null +++ b/frontend/src/Artist/Index/Overview/ArtistIndexOverviewsConnector.js @@ -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); diff --git a/frontend/src/Artist/Index/Overview/Options/ArtistIndexOverviewOptionsModal.js b/frontend/src/Artist/Index/Overview/Options/ArtistIndexOverviewOptionsModal.js new file mode 100644 index 000000000..9ca575185 --- /dev/null +++ b/frontend/src/Artist/Index/Overview/Options/ArtistIndexOverviewOptionsModal.js @@ -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 ( + + + + ); +} + +ArtistIndexOverviewOptionsModal.propTypes = { + isOpen: PropTypes.bool.isRequired, + onModalClose: PropTypes.func.isRequired +}; + +export default ArtistIndexOverviewOptionsModal; diff --git a/frontend/src/Artist/Index/Overview/Options/ArtistIndexOverviewOptionsModalContent.js b/frontend/src/Artist/Index/Overview/Options/ArtistIndexOverviewOptionsModalContent.js new file mode 100644 index 000000000..b5bf02b45 --- /dev/null +++ b/frontend/src/Artist/Index/Overview/Options/ArtistIndexOverviewOptionsModalContent.js @@ -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 ( + + + Overview Options + + + +
+ + Poster Size + + + + + + Detailed Progress Bar + + + + + + Show Quality Profile + + + + + + Show Previous Airing + + + + + + Show Date Added + + + + + + Show Season Count + + + + + + Show Path + + + + + + Show Size on Disk + + + +
+
+ + + + +
+ ); + } +} + +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; diff --git a/frontend/src/Artist/Index/Overview/Options/ArtistIndexOverviewOptionsModalContentConnector.js b/frontend/src/Artist/Index/Overview/Options/ArtistIndexOverviewOptionsModalContentConnector.js new file mode 100644 index 000000000..70c30dba6 --- /dev/null +++ b/frontend/src/Artist/Index/Overview/Options/ArtistIndexOverviewOptionsModalContentConnector.js @@ -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); diff --git a/frontend/src/Artist/Index/Posters/ArtistIndexPoster.js b/frontend/src/Artist/Index/Posters/ArtistIndexPoster.js index 134b7926a..6ec411eac 100644 --- a/frontend/src/Artist/Index/Posters/ArtistIndexPoster.js +++ b/frontend/src/Artist/Index/Posters/ArtistIndexPoster.js @@ -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 {
- {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, diff --git a/frontend/src/Artist/Index/Posters/ArtistIndexPosterProgressBar.css b/frontend/src/Artist/Index/Posters/ArtistIndexPosterProgressBar.css deleted file mode 100644 index dbf3499ab..000000000 --- a/frontend/src/Artist/Index/Posters/ArtistIndexPosterProgressBar.css +++ /dev/null @@ -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; -} diff --git a/frontend/src/Artist/Index/Posters/ArtistIndexPosters.js b/frontend/src/Artist/Index/Posters/ArtistIndexPosters.js index 47424cc54..9f870a55b 100644 --- a/frontend/src/Artist/Index/Posters/ArtistIndexPosters.js +++ b/frontend/src/Artist/Index/Posters/ArtistIndexPosters.js @@ -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); diff --git a/frontend/src/Artist/Index/Banners/ArtistIndexBannerProgressBar.css b/frontend/src/Artist/Index/ProgressBar/ArtistIndexProgressBar.css similarity index 100% rename from frontend/src/Artist/Index/Banners/ArtistIndexBannerProgressBar.css rename to frontend/src/Artist/Index/ProgressBar/ArtistIndexProgressBar.css diff --git a/frontend/src/Artist/Index/Posters/ArtistIndexPosterProgressBar.js b/frontend/src/Artist/Index/ProgressBar/ArtistIndexProgressBar.js similarity index 85% rename from frontend/src/Artist/Index/Posters/ArtistIndexPosterProgressBar.js rename to frontend/src/Artist/Index/ProgressBar/ArtistIndexProgressBar.js index 1ab803251..89d85f80a 100644 --- a/frontend/src/Artist/Index/Posters/ArtistIndexPosterProgressBar.js +++ b/frontend/src/Artist/Index/ProgressBar/ArtistIndexProgressBar.js @@ -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; diff --git a/frontend/src/Calendar/CalendarPage.js b/frontend/src/Calendar/CalendarPage.js index b54900b17..bf1e5cfe8 100644 --- a/frontend/src/Calendar/CalendarPage.js +++ b/frontend/src/Calendar/CalendarPage.js @@ -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} > - + { + this.state.width > 0 ? + : +
+ } diff --git a/frontend/src/Components/DescriptionList/DescriptionListItemDescription.css b/frontend/src/Components/DescriptionList/DescriptionListItemDescription.css index 582eaff24..b23415a76 100644 --- a/frontend/src/Components/DescriptionList/DescriptionListItemDescription.css +++ b/frontend/src/Components/DescriptionList/DescriptionListItemDescription.css @@ -1,5 +1,5 @@ .description { - line-height: 1.528571429; + line-height: $lineHeight; } .description { diff --git a/frontend/src/Components/DescriptionList/DescriptionListItemTitle.css b/frontend/src/Components/DescriptionList/DescriptionListItemTitle.css index efe6d8103..e496e463d 100644 --- a/frontend/src/Components/DescriptionList/DescriptionListItemTitle.css +++ b/frontend/src/Components/DescriptionList/DescriptionListItemTitle.css @@ -1,5 +1,5 @@ .title { - line-height: 1.528571429; + line-height: $lineHeight; } .title { diff --git a/frontend/src/Components/Form/PathInput.js b/frontend/src/Components/Form/PathInput.js index 19ae9f94f..84e8209d8 100644 --- a/frontend/src/Components/Form/PathInput.js +++ b/frontend/src/Components/Form/PathInput.js @@ -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 }) => { diff --git a/frontend/src/Components/SignalRConnector.js b/frontend/src/Components/SignalRConnector.js index 75ecbddab..ad1f28987 100644 --- a/frontend/src/Components/SignalRConnector.js +++ b/frontend/src/Components/SignalRConnector.js @@ -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 diff --git a/frontend/src/Helpers/Props/icons.js b/frontend/src/Helpers/Props/icons.js index 1ca253a4b..65686cc8d 100644 --- a/frontend/src/Helpers/Props/icons.js +++ b/frontend/src/Helpers/Props/icons.js @@ -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'; diff --git a/frontend/src/Settings/UI/UISettings.js b/frontend/src/Settings/UI/UISettings.js index 7d0b09932..c443e4819 100644 --- a/frontend/src/Settings/UI/UISettings.js +++ b/frontend/src/Settings/UI/UISettings.js @@ -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 = [ diff --git a/frontend/src/Store/Actions/actionTypes.js b/frontend/src/Store/Actions/actionTypes.js index 7f25e29e4..5ff4302ca 100644 --- a/frontend/src/Store/Actions/actionTypes.js +++ b/frontend/src/Store/Actions/actionTypes.js @@ -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'; diff --git a/frontend/src/Store/Actions/artistIndexActions.js b/frontend/src/Store/Actions/artistIndexActions.js index e657dd5f7..2e15d6c0b 100644 --- a/frontend/src/Store/Actions/artistIndexActions.js +++ b/frontend/src/Store/Actions/artistIndexActions.js @@ -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); diff --git a/frontend/src/Store/Actions/calendarActionHandlers.js b/frontend/src/Store/Actions/calendarActionHandlers.js index 168b4c6c2..bb619858a 100644 --- a/frontend/src/Store/Actions/calendarActionHandlers.js +++ b/frontend/src/Store/Actions/calendarActionHandlers.js @@ -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 })); }; diff --git a/frontend/src/Store/Actions/queueActionHandlers.js b/frontend/src/Store/Actions/queueActionHandlers.js index b5b44e8ec..494942b61 100644 --- a/frontend/src/Store/Actions/queueActionHandlers.js +++ b/frontend/src/Store/Actions/queueActionHandlers.js @@ -206,7 +206,10 @@ const queueActionHandlers = { }); promise.done((data) => { - dispatch(fetchQueue()); + dispatch(batchActions([ + set({ section, isRemoving: false }), + fetchQueue() + ])); }); promise.fail((xhr) => { diff --git a/frontend/src/Store/Reducers/artistIndexReducers.js b/frontend/src/Store/Reducers/artistIndexReducers.js index 5978f2398..cdc45e7e8 100644 --- a/frontend/src/Store/Reducers/artistIndexReducers.js +++ b/frontend/src/Store/Reducers/artistIndexReducers.js @@ -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); diff --git a/frontend/src/Styles/Variables/dimensions.js b/frontend/src/Styles/Variables/dimensions.js index fed51a1df..54282902c 100644 --- a/frontend/src/Styles/Variables/dimensions.js +++ b/frontend/src/Styles/Variables/dimensions.js @@ -36,8 +36,10 @@ module.exports = { progressBarLargeHeight: '20px', // Jump Bar - jumpBarItemHeight: '25px' - - // Series + jumpBarItemHeight: '25px', + // Artist + artistIndexColumnPadding: '20px', + artistIndexColumnPaddingSmallScreen: '10px', + artistIndexOverviewInfoRowHeight: '21px' }; diff --git a/frontend/src/Styles/Variables/fonts.js b/frontend/src/Styles/Variables/fonts.js index c4060b661..6221ce5f1 100644 --- a/frontend/src/Styles/Variables/fonts.js +++ b/frontend/src/Styles/Variables/fonts.js @@ -7,5 +7,7 @@ module.exports = { extraSmallFontSize: '11px', smallFontSize: '12px', defaultFontSize: '14px', - largeFontSize: '16px' + largeFontSize: '16px', + + lineHeight: '1.528571429' }; diff --git a/package.json b/package.json index a2f2330e2..e4b40c9ac 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/Lidarr.Api.V3/Calendar/CalendarFeedModule.cs b/src/Lidarr.Api.V3/Calendar/CalendarFeedModule.cs index e2da574be..c4e948bb2 100644 --- a/src/Lidarr.Api.V3/Calendar/CalendarFeedModule.cs +++ b/src/Lidarr.Api.V3/Calendar/CalendarFeedModule.cs @@ -81,7 +81,7 @@ namespace Lidarr.Api.V3.Calendar //occurrence.Description = album.Overview; //occurrence.Categories = new List() { 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}"; } diff --git a/yarn.lock b/yarn.lock index 14d9246d6..bb9d18a90 100644 --- a/yarn.lock +++ b/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"