Fixed: Misc Calendar Improvements

pull/3991/head
Qstick 5 years ago
parent b2c1dbf3ab
commit 5a5e896eb4

@ -37,22 +37,6 @@
margin-right: 10px; margin-right: 10px;
} }
.episodeTitle {
flex: 1 1 1px;
}
.seasonEpisodeNumber {
flex: 0 0 100px;
}
.episodeSeparator {
display: none;
}
.absoluteEpisodeNumber {
margin-left: 3px;
}
.statusIcon { .statusIcon {
margin-left: 3px; margin-left: 3px;
} }
@ -73,18 +57,10 @@
composes: unmonitored from '~Calendar/Events/CalendarEvent.css'; composes: unmonitored from '~Calendar/Events/CalendarEvent.css';
} }
.onAir {
composes: onAir from '~Calendar/Events/CalendarEvent.css';
}
.missing { .missing {
composes: missing from '~Calendar/Events/CalendarEvent.css'; composes: missing from '~Calendar/Events/CalendarEvent.css';
} }
.premiere {
composes: premiere from '~Calendar/Events/CalendarEvent.css';
}
@media only screen and (max-width: $breakpointSmall) { @media only screen and (max-width: $breakpointSmall) {
.event { .event {
flex-direction: column; flex-direction: column;
@ -101,16 +77,7 @@
.date, .date,
.time, .time,
.seriesTitle { .movieTitle {
flex: 0 0 100%; flex: 0 0 100%;
} }
.seasonEpisodeNumber {
flex: 0 0 auto;
}
.episodeSeparator {
display: inline-block;
margin: 0 5px;
}
} }

@ -44,18 +44,10 @@
font-size: $defaultFontSize; font-size: $defaultFontSize;
} }
.absoluteEpisodeNumber {
margin-left: 3px;
}
.statusIcon { .statusIcon {
margin-left: 3px; margin-left: 3px;
} }
.airTime {
color: $calendarTextDim;
}
/* /*
* Status * Status
*/ */
@ -97,7 +89,7 @@
} }
} }
.unaired { .unreleased {
border-left-color: $primaryColor !important; border-left-color: $primaryColor !important;
&:global(.colorImpaired) { &:global(.colorImpaired) {

@ -11,17 +11,6 @@ import styles from './CalendarEvent.css';
class CalendarEvent extends Component { class CalendarEvent extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
// isDetailsModalOpen: false
};
}
// //
// Render // Render
@ -138,7 +127,6 @@ CalendarEvent.propTypes = {
timeFormat: PropTypes.string.isRequired, timeFormat: PropTypes.string.isRequired,
colorImpairedMode: PropTypes.bool.isRequired, colorImpairedMode: PropTypes.bool.isRequired,
date: PropTypes.string.isRequired date: PropTypes.string.isRequired
// onEventModalOpenToggle: PropTypes.func.isRequired
}; };
CalendarEvent.defaultProps = { CalendarEvent.defaultProps = {

@ -1,6 +1,5 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import createMovieFileSelector from 'Store/Selectors/createMovieFileSelector';
import createMovieSelector from 'Store/Selectors/createMovieSelector'; import createMovieSelector from 'Store/Selectors/createMovieSelector';
import createQueueItemSelector from 'Store/Selectors/createQueueItemSelector'; import createQueueItemSelector from 'Store/Selectors/createQueueItemSelector';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector'; import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
@ -10,13 +9,11 @@ function createMapStateToProps() {
return createSelector( return createSelector(
(state) => state.calendar.options, (state) => state.calendar.options,
createMovieSelector(), createMovieSelector(),
createMovieFileSelector(),
createQueueItemSelector(), createQueueItemSelector(),
createUISettingsSelector(), createUISettingsSelector(),
(calendarOptions, movie, movieFile, queueItem, uiSettings) => { (calendarOptions, movie, queueItem, uiSettings) => {
return { return {
movie, movie,
movieFile,
queueItem, queueItem,
...calendarOptions, ...calendarOptions,
timeFormat: uiSettings.timeFormat, timeFormat: uiSettings.timeFormat,

@ -1,87 +0,0 @@
.eventGroup {
overflow-x: hidden;
margin: 4px 2px;
padding: 5px;
border-bottom: 1px solid $borderColor;
border-left: 4px solid $borderColor;
font-size: 12px;
}
.info,
.airingInfo {
display: flex;
}
.seriesTitle {
@add-mixin truncate;
flex: 1 0 1px;
margin-right: 10px;
color: #3a3f51;
font-size: $defaultFontSize;
}
.airTime {
flex: 1 0 1px;
color: $calendarTextDim;
}
.episodeInfo {
margin-left: 10px;
color: $calendarTextDim;
}
.absoluteEpisodeNumber {
margin-left: 3px;
}
.expandContainerInline {
display: flex;
justify-content: flex-end;
flex: 1 0 20px;
}
.expandContainer,
.collapseContainer {
display: flex;
justify-content: center;
}
.collapseContainer {
margin-bottom: 5px;
}
.statusIcon {
margin-left: 3px;
}
/*
* Status
*/
.downloaded {
composes: downloaded from '~Calendar/Events/CalendarEvent.css';
}
.downloading {
composes: downloading from '~Calendar/Events/CalendarEvent.css';
}
.unmonitored {
composes: unmonitored from '~Calendar/Events/CalendarEvent.css';
}
.onAir {
composes: onAir from '~Calendar/Events/CalendarEvent.css';
}
.missing {
composes: missing from '~Calendar/Events/CalendarEvent.css';
}
.premiere {
composes: premiere from '~Calendar/Events/CalendarEvent.css';
}
.unaired {
composes: unaired from '~Calendar/Events/CalendarEvent.css';
}

@ -1,200 +0,0 @@
import moment from 'moment';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import classNames from 'classnames';
import { icons, kinds } from 'Helpers/Props';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import getStatusStyle from 'Calendar/getStatusStyle';
import CalendarEventConnector from 'Calendar/Events/CalendarEventConnector';
import styles from './CalendarEventGroup.css';
function getEventsInfo(events) {
let files = 0;
let queued = 0;
let monitored = 0;
let absoluteEpisodeNumbers = 0;
events.forEach((event) => {
if (event.episodeFileId) {
files++;
}
if (event.queued) {
queued++;
}
if (event.monitored) {
monitored++;
}
if (event.absoluteEpisodeNumber) {
absoluteEpisodeNumbers++;
}
});
return {
allDownloaded: files === events.length,
anyQueued: queued > 0,
anyMonitored: monitored > 0,
allAbsoluteEpisodeNumbers: absoluteEpisodeNumbers === events.length
};
}
class CalendarEventGroup extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
isExpanded: false
};
}
//
// Listeners
onExpandPress = () => {
this.setState({ isExpanded: !this.state.isExpanded });
}
//
// Render
render() {
const {
series,
events,
isDownloading,
showEpisodeInformation,
showFinaleIcon,
colorImpairedMode,
onEventModalOpenToggle
} = this.props;
const { isExpanded } = this.state;
const {
allDownloaded,
anyQueued,
anyMonitored
} = getEventsInfo(events);
const anyDownloading = isDownloading || anyQueued;
const firstEpisode = events[0];
const lastEpisode = events[events.length -1];
const airDateUtc = firstEpisode.airDateUtc;
const startTime = moment(airDateUtc);
const endTime = moment(lastEpisode.airDateUtc).add(series.runtime, 'minutes');
const seasonNumber = firstEpisode.seasonNumber;
const statusStyle = getStatusStyle(allDownloaded, anyDownloading, startTime, endTime, anyMonitored);
if (isExpanded) {
return (
<div>
{
events.map((event) => {
if (event.isGroup) {
return null;
}
return (
<CalendarEventConnector
key={event.id}
episodeId={event.id}
{...event}
onEventModalOpenToggle={onEventModalOpenToggle}
/>
);
})
}
<Link
className={styles.collapseContainer}
component="div"
onPress={this.onExpandPress}
>
<Icon
name={icons.COLLAPSE}
/>
</Link>
</div>
);
}
return (
<div
className={classNames(
styles.eventGroup,
styles[statusStyle],
colorImpairedMode && 'colorImpaired'
)}
>
<div className={styles.info}>
<div className={styles.seriesTitle}>
{series.title}
</div>
{
anyDownloading &&
<Icon
className={styles.statusIcon}
name={icons.DOWNLOADING}
title="An episode is downloading"
/>
}
{
firstEpisode.episodeNumber === 1 && seasonNumber > 0 &&
<Icon
className={styles.statusIcon}
name={icons.INFO}
kind={kinds.INFO}
title={seasonNumber === 1 ? 'Series Premiere' : 'Season Premiere'}
/>
}
{
showFinaleIcon &&
lastEpisode.episodeNumber !== 1 &&
seasonNumber > 0 &&
lastEpisode.episodeNumber === series.seasons.find((season) => season.seasonNumber === seasonNumber).statistics.totalEpisodeCount &&
<Icon
className={styles.statusIcon}
name={icons.INFO}
kind={kinds.WARNING}
title={series.status === 'ended' ? 'Series finale' : 'Season finale'}
/>
}
</div>
{
showEpisodeInformation &&
<Link
className={styles.expandContainer}
component="div"
onPress={this.onExpandPress}
>
<Icon
name={icons.EXPAND}
/>
</Link>
}
</div>
);
}
}
CalendarEventGroup.propTypes = {
series: PropTypes.object.isRequired,
events: PropTypes.arrayOf(PropTypes.object).isRequired,
isDownloading: PropTypes.bool.isRequired,
showEpisodeInformation: PropTypes.bool.isRequired,
showFinaleIcon: PropTypes.bool.isRequired,
timeFormat: PropTypes.string.isRequired,
colorImpairedMode: PropTypes.bool.isRequired,
onEventModalOpenToggle: PropTypes.func.isRequired
};
export default CalendarEventGroup;

@ -1,37 +0,0 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createMovieSelector from 'Store/Selectors/createMovieSelector';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import CalendarEventGroup from './CalendarEventGroup';
function createIsDownloadingSelector() {
return createSelector(
(state, { movieIds }) => movieIds,
(state) => state.queue.details,
(movieIds, details) => {
return details.items.some((item) => {
return item.movie && movieIds.includes(item.movie.id);
});
}
);
}
function createMapStateToProps() {
return createSelector(
(state) => state.calendar.options,
createMovieSelector(),
createIsDownloadingSelector(),
createUISettingsSelector(),
(calendarOptions, movie, isDownloading, uiSettings) => {
return {
movie,
isDownloading,
...calendarOptions,
timeFormat: uiSettings.timeFormat,
colorImpairedMode: uiSettings.enableColorImpairedMode
};
}
);
}
export default connect(createMapStateToProps)(CalendarEventGroup);

@ -7,34 +7,11 @@ import styles from './Legend.css';
function Legend(props) { function Legend(props) {
const { const {
showFinaleIcon,
showSpecialIcon,
showCutoffUnmetIcon, showCutoffUnmetIcon,
colorImpairedMode colorImpairedMode
} = props; } = props;
const iconsToShow = []; const iconsToShow = [];
if (showFinaleIcon) {
iconsToShow.push(
<LegendIconItem
name="Finale"
icon={icons.INFO}
kind={kinds.WARNING}
tooltip="Series or season finale"
/>
);
}
if (showSpecialIcon) {
iconsToShow.push(
<LegendIconItem
name="Special"
icon={icons.INFO}
kind={kinds.PINK}
tooltip="Special episode"
/>
);
}
if (showCutoffUnmetIcon) { if (showCutoffUnmetIcon) {
iconsToShow.push( iconsToShow.push(
@ -51,8 +28,8 @@ function Legend(props) {
<div className={styles.legend}> <div className={styles.legend}>
<div> <div>
<LegendItem <LegendItem
status="unaired" status="unreleased"
tooltip="Movie hasn't aired yet" tooltip="Movie hasn't released yet"
colorImpairedMode={colorImpairedMode} colorImpairedMode={colorImpairedMode}
/> />
@ -77,22 +54,10 @@ function Legend(props) {
/> />
</div> </div>
<div>
<LegendIconItem
name="Premiere"
icon={icons.INFO}
kind={kinds.INFO}
tooltip="Series or season premiere"
/>
{iconsToShow[0]}
</div>
{ {
iconsToShow.length > 1 && iconsToShow.length > 0 &&
<div> <div>
{iconsToShow[1]} {iconsToShow[0]}
{iconsToShow[2]}
</div> </div>
} }
</div> </div>
@ -100,8 +65,6 @@ function Legend(props) {
} }
Legend.propTypes = { Legend.propTypes = {
showFinaleIcon: PropTypes.bool.isRequired,
showSpecialIcon: PropTypes.bool.isRequired,
showCutoffUnmetIcon: PropTypes.bool.isRequired, showCutoffUnmetIcon: PropTypes.bool.isRequired,
colorImpairedMode: PropTypes.bool.isRequired colorImpairedMode: PropTypes.bool.isRequired
}; };

@ -24,18 +24,10 @@
composes: unmonitored from '~Calendar/Events/CalendarEvent.css'; composes: unmonitored from '~Calendar/Events/CalendarEvent.css';
} }
.onAir {
composes: onAir from '~Calendar/Events/CalendarEvent.css';
}
.missing { .missing {
composes: missing from '~Calendar/Events/CalendarEvent.css'; composes: missing from '~Calendar/Events/CalendarEvent.css';
} }
.premiere { .unreleased {
composes: premiere from '~Calendar/Events/CalendarEvent.css'; composes: unreleased from '~Calendar/Events/CalendarEvent.css';
}
.unaired {
composes: unaired from '~Calendar/Events/CalendarEvent.css';
} }

@ -203,8 +203,6 @@ class CalendarOptionsModalContent extends Component {
CalendarOptionsModalContent.propTypes = { CalendarOptionsModalContent.propTypes = {
showMovieInformation: PropTypes.bool.isRequired, showMovieInformation: PropTypes.bool.isRequired,
showFinaleIcon: PropTypes.bool.isRequired,
showSpecialIcon: PropTypes.bool.isRequired,
showCutoffUnmetIcon: PropTypes.bool.isRequired, showCutoffUnmetIcon: PropTypes.bool.isRequired,
firstDayOfWeek: PropTypes.number.isRequired, firstDayOfWeek: PropTypes.number.isRequired,
calendarWeekColumnHeader: PropTypes.string.isRequired, calendarWeekColumnHeader: PropTypes.string.isRequired,

@ -20,7 +20,7 @@ function getStatusStyle(hasFile, downloading, startTime, isMonitored) {
return 'missing'; return 'missing';
} }
return 'unaired'; return 'unreleased';
} }
export default getStatusStyle; export default getStatusStyle;

@ -17,7 +17,6 @@ import ModalFooter from 'Components/Modal/ModalFooter';
function getUrls(state) { function getUrls(state) {
const { const {
unmonitored, unmonitored,
premieresOnly,
asAllDay, asAllDay,
tags tags
} = state; } = state;
@ -28,10 +27,6 @@ function getUrls(state) {
icalUrl += 'unmonitored=true&'; icalUrl += 'unmonitored=true&';
} }
if (premieresOnly) {
icalUrl += 'premieresOnly=true&';
}
if (asAllDay) { if (asAllDay) {
icalUrl += 'asAllDay=true&'; icalUrl += 'asAllDay=true&';
} }
@ -61,7 +56,6 @@ class CalendarLinkModalContent extends Component {
const defaultState = { const defaultState = {
unmonitored: false, unmonitored: false,
premieresOnly: false,
asAllDay: false, asAllDay: false,
tags: [] tags: []
}; };
@ -105,7 +99,6 @@ class CalendarLinkModalContent extends Component {
const { const {
unmonitored, unmonitored,
premieresOnly,
asAllDay, asAllDay,
tags, tags,
iCalHttpUrl, iCalHttpUrl,
@ -132,18 +125,6 @@ class CalendarLinkModalContent extends Component {
/> />
</FormGroup> </FormGroup>
<FormGroup>
<FormLabel>Season Premieres Only</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="premieresOnly"
value={premieresOnly}
helpText="Only the first episode in a season will be in the feed"
onChange={this.onInputChange}
/>
</FormGroup>
<FormGroup> <FormGroup>
<FormLabel>Show as All-Day Events</FormLabel> <FormLabel>Show as All-Day Events</FormLabel>

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Nancy; using Nancy;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Movies; using NzbDrone.Core.Movies;
using NzbDrone.SignalR; using NzbDrone.SignalR;
using Radarr.Api.V3.Movies; using Radarr.Api.V3.Movies;
@ -11,13 +12,16 @@ namespace Radarr.Api.V3.Calendar
{ {
public class CalendarModule : RadarrRestModuleWithSignalR<MovieResource, Movie> public class CalendarModule : RadarrRestModuleWithSignalR<MovieResource, Movie>
{ {
protected readonly IMovieService _moviesService; private readonly IMovieService _moviesService;
private readonly IUpgradableSpecification _qualityUpgradableSpecification;
public CalendarModule(IBroadcastSignalRMessage signalR, public CalendarModule(IBroadcastSignalRMessage signalR,
IMovieService moviesService) IMovieService moviesService,
IUpgradableSpecification qualityUpgradableSpecification)
: base(signalR, "calendar") : base(signalR, "calendar")
{ {
_moviesService = moviesService; _moviesService = moviesService;
_qualityUpgradableSpecification = qualityUpgradableSpecification;
GetResourceAll = GetCalendar; GetResourceAll = GetCalendar;
} }
@ -59,7 +63,7 @@ namespace Radarr.Api.V3.Calendar
return null; return null;
} }
var resource = movie.ToResource(); var resource = movie.ToResource(_qualityUpgradableSpecification);
return resource; return resource;
} }

Loading…
Cancel
Save