1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2024-10-30 07:22:35 +01:00

New: Calendar option for full color events

This commit is contained in:
Mark McDowall 2021-03-27 18:05:17 -07:00
parent 66c805feaf
commit 0210b5c5c1
12 changed files with 238 additions and 102 deletions

View File

@ -1,3 +1,5 @@
$fullColorGradient: rgba(244, 245, 246, 0.2);
.event {
overflow-x: hidden;
margin: 4px 2px;
@ -36,6 +38,11 @@
margin-left: 3px;
}
.statusContainer {
display: flex;
align-items: center;
}
.statusIcon {
margin-left: 3px;
}
@ -51,6 +58,10 @@
.downloaded {
border-left-color: $successColor !important;
&:global(.fullColor) {
background-color: rgba(39, 194, 76, 0.4) !important;
}
&:global(.colorImpaired) {
border-left-color: color($successColor, saturation(+15%)) !important;
}
@ -58,37 +69,73 @@
.downloading {
border-left-color: $purple !important;
&:global(.fullColor) {
background-color: rgba(122, 67, 182, 0.4) !important;
}
}
.unmonitored {
border-left-color: $gray !important;
&:global(.fullColor) {
background-color: rgba(173, 173, 173, 0.5) !important;
}
&:global(.colorImpaired) {
background: repeating-linear-gradient(45deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
}
&:global(.fullColor.colorImpaired) {
background: repeating-linear-gradient(45deg, $fullColorGradient, $fullColorGradient 5px, transparent 5px, transparent 10px);
}
}
.onAir {
border-left-color: $warningColor !important;
&:global(.fullColor) {
background-color: rgba(255, 165, 0, 0.6) !important;
}
&:global(.colorImpaired) {
background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
}
&:global(.fullColor.colorImpaired) {
background: repeating-linear-gradient(90deg, $fullColorGradient, $fullColorGradient 5px, transparent 5px, transparent 10px);
}
}
.missing {
border-left-color: $dangerColor !important;
&:global(.fullColor) {
background-color: rgba(240, 80, 80, 0.6) !important;
}
&:global(.colorImpaired) {
border-left-color: color($dangerColor saturation(+15%)) !important;
background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
}
&:global(.fullColor.colorImpaired) {
background: repeating-linear-gradient(90deg, $fullColorGradient, $fullColorGradient 5px, transparent 5px, transparent 10px);
}
}
.unaired {
border-left-color: $primaryColor !important;
&:global(.fullColor) {
background-color: rgba(93, 156, 236, 0.4) !important;
}
&:global(.colorImpaired) {
background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
}
&:global(.fullColor.colorImpaired) {
background: repeating-linear-gradient(90deg, $fullColorGradient, $fullColorGradient 5px, transparent 5px, transparent 10px);
}
}

View File

@ -1,6 +1,6 @@
import moment from 'moment';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import React, { Component, Fragment } from 'react';
import classNames from 'classnames';
import { icons, kinds } from 'Helpers/Props';
import formatTime from 'Utilities/Date/formatTime';
@ -62,6 +62,7 @@ class CalendarEvent extends Component {
showFinaleIcon,
showSpecialIcon,
showCutoffUnmetIcon,
fullColorEvents,
timeFormat,
colorImpairedMode
} = this.props;
@ -80,12 +81,13 @@ class CalendarEvent extends Component {
const seasonStatistics = season.statistics || {};
return (
<div>
<Fragment>
<Link
className={classNames(
styles.event,
styles[statusStyle],
colorImpairedMode && 'colorImpaired'
colorImpairedMode && 'colorImpaired',
fullColorEvents && 'fullColor'
)}
component="div"
onPress={this.onPress}
@ -95,95 +97,107 @@ class CalendarEvent extends Component {
{series.title}
</div>
{
missingAbsoluteNumber &&
<Icon
className={styles.statusIcon}
name={icons.WARNING}
title="Episode does not have an absolute episode number"
/>
}
<div className={styles.statusContainer}>
{
missingAbsoluteNumber ?
<Icon
className={styles.statusIcon}
name={icons.WARNING}
title="Episode does not have an absolute episode number"
/> :
null
}
{
!!queueItem &&
<span className={styles.statusIcon}>
<CalendarEventQueueDetails
{...queueItem}
/>
</span>
}
{
queueItem ?
<span className={styles.statusIcon}>
<CalendarEventQueueDetails
{...queueItem}
/>
</span> :
null
}
{
!queueItem && grabbed &&
<Icon
className={styles.statusIcon}
name={icons.DOWNLOADING}
title="Episode is downloading"
/>
}
{
!queueItem && grabbed ?
<Icon
className={styles.statusIcon}
name={icons.DOWNLOADING}
title="Episode is downloading"
/> :
null
}
{
showCutoffUnmetIcon &&
!!episodeFile &&
episodeFile.qualityCutoffNotMet &&
<Icon
className={styles.statusIcon}
name={icons.EPISODE_FILE}
kind={kinds.WARNING}
title="Quality cutoff has not been met"
/>
}
{
showCutoffUnmetIcon &&
!!episodeFile &&
episodeFile.qualityCutoffNotMet ?
<Icon
className={styles.statusIcon}
name={icons.EPISODE_FILE}
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
title="Quality cutoff has not been met"
/> :
null
}
{
showCutoffUnmetIcon &&
!!episodeFile &&
episodeFile.languageCutoffNotMet &&
!episodeFile.qualityCutoffNotMet &&
<Icon
className={styles.statusIcon}
name={icons.EPISODE_FILE}
kind={kinds.WARNING}
title="Language cutoff has not been met"
/>
}
{
showCutoffUnmetIcon &&
!!episodeFile &&
episodeFile.languageCutoffNotMet &&
!episodeFile.qualityCutoffNotMet ?
<Icon
className={styles.statusIcon}
name={icons.EPISODE_FILE}
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
title="Language cutoff has not been met"
/> :
null
}
{
episodeNumber === 1 && seasonNumber > 0 &&
<Icon
className={styles.statusIcon}
name={icons.INFO}
kind={kinds.INFO}
title={seasonNumber === 1 ? 'Series premiere' : 'Season premiere'}
/>
}
{
episodeNumber === 1 && seasonNumber > 0 ?
<Icon
className={styles.statusIcon}
name={icons.INFO}
kind={kinds.INFO}
darken={fullColorEvents}
title={seasonNumber === 1 ? 'Series premiere' : 'Season premiere'}
/> :
null
}
{
showFinaleIcon &&
episodeNumber !== 1 &&
seasonNumber > 0 &&
episodeNumber === seasonStatistics.totalEpisodeCount &&
<Icon
className={styles.statusIcon}
name={icons.INFO}
kind={kinds.WARNING}
title={series.status === 'ended' ? 'Series finale' : 'Season finale'}
/>
}
{
showFinaleIcon &&
episodeNumber !== 1 &&
seasonNumber > 0 &&
episodeNumber === seasonStatistics.totalEpisodeCount ?
<Icon
className={styles.statusIcon}
name={icons.INFO}
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
title={series.status === 'ended' ? 'Series finale' : 'Season finale'}
/> :
null
}
{
showSpecialIcon &&
(episodeNumber === 0 || seasonNumber === 0) &&
<Icon
className={styles.statusIcon}
name={icons.INFO}
kind={kinds.PINK}
title="Special"
/>
}
{
showSpecialIcon &&
(episodeNumber === 0 || seasonNumber === 0) ?
<Icon
className={styles.statusIcon}
name={icons.INFO}
kind={kinds.PINK}
darken={fullColorEvents}
title="Special"
/> :
null
}
</div>
</div>
{
showEpisodeInformation &&
showEpisodeInformation ?
<div className={styles.episodeInfo}>
<div className={styles.episodeTitle}>
{title}
@ -193,11 +207,12 @@ class CalendarEvent extends Component {
{seasonNumber}x{padNumber(episodeNumber, 2)}
{
series.seriesType === 'anime' && absoluteEpisodeNumber &&
<span className={styles.absoluteEpisodeNumber}>({absoluteEpisodeNumber})</span>
series.seriesType === 'anime' && absoluteEpisodeNumber ?
<span className={styles.absoluteEpisodeNumber}>({absoluteEpisodeNumber})</span> : null
}
</div>
</div>
</div> :
null
}
<div className={styles.airTime}>
@ -214,7 +229,7 @@ class CalendarEvent extends Component {
showOpenSeriesButton={true}
onModalClose={this.onDetailsModalClose}
/>
</div>
</Fragment>
);
}
}
@ -236,6 +251,7 @@ CalendarEvent.propTypes = {
showFinaleIcon: PropTypes.bool.isRequired,
showSpecialIcon: PropTypes.bool.isRequired,
showCutoffUnmetIcon: PropTypes.bool.isRequired,
fullColorEvents: PropTypes.bool.isRequired,
timeFormat: PropTypes.string.isRequired,
colorImpairedMode: PropTypes.bool.isRequired,
onEventModalOpenToggle: PropTypes.func.isRequired

View File

@ -74,6 +74,7 @@ class CalendarEventGroup extends Component {
showEpisodeInformation,
showFinaleIcon,
timeFormat,
fullColorEvents,
colorImpairedMode,
onEventModalOpenToggle
} = this.props;
@ -133,7 +134,8 @@ class CalendarEventGroup extends Component {
className={classNames(
styles.eventGroup,
styles[statusStyle],
colorImpairedMode && 'colorImpaired'
colorImpairedMode && 'colorImpaired',
fullColorEvents && 'fullColor'
)}
>
<div className={styles.info}>
@ -144,7 +146,7 @@ class CalendarEventGroup extends Component {
{
isMissingAbsoluteNumber &&
<Icon
className={styles.statusIcon}
containerClassName={styles.statusIcon}
name={icons.WARNING}
title="Episode does not have an absolute episode number"
/>
@ -153,7 +155,7 @@ class CalendarEventGroup extends Component {
{
anyDownloading &&
<Icon
className={styles.statusIcon}
containerClassName={styles.statusIcon}
name={icons.DOWNLOADING}
title="An episode is downloading"
/>
@ -162,9 +164,10 @@ class CalendarEventGroup extends Component {
{
firstEpisode.episodeNumber === 1 && seasonNumber > 0 &&
<Icon
className={styles.statusIcon}
containerClassName={styles.statusIcon}
name={icons.INFO}
kind={kinds.INFO}
darken={fullColorEvents}
title={seasonNumber === 1 ? 'Series Premiere' : 'Season Premiere'}
/>
}
@ -175,9 +178,9 @@ class CalendarEventGroup extends Component {
seasonNumber > 0 &&
lastEpisode.episodeNumber === series.seasons.find((season) => season.seasonNumber === seasonNumber).statistics.totalEpisodeCount &&
<Icon
className={styles.statusIcon}
containerClassName={styles.statusIcon}
name={icons.INFO}
kind={kinds.WARNING}
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
title={series.status === 'ended' ? 'Series finale' : 'Season finale'}
/>
}
@ -237,6 +240,7 @@ CalendarEventGroup.propTypes = {
isDownloading: PropTypes.bool.isRequired,
showEpisodeInformation: PropTypes.bool.isRequired,
showFinaleIcon: PropTypes.bool.isRequired,
fullColorEvents: PropTypes.bool.isRequired,
timeFormat: PropTypes.string.isRequired,
colorImpairedMode: PropTypes.bool.isRequired,
onEventModalOpenToggle: PropTypes.func.isRequired

View File

@ -7,19 +7,23 @@ import styles from './Legend.css';
function Legend(props) {
const {
view,
showFinaleIcon,
showSpecialIcon,
showCutoffUnmetIcon,
fullColorEvents,
colorImpairedMode
} = props;
const iconsToShow = [];
const isAgendaView = view === 'agenda';
if (showFinaleIcon) {
iconsToShow.push(
<LegendIconItem
name="Finale"
icon={icons.INFO}
kind={kinds.WARNING}
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
tooltip="Series or season finale"
/>
);
@ -31,6 +35,7 @@ function Legend(props) {
name="Special"
icon={icons.INFO}
kind={kinds.PINK}
darken={fullColorEvents}
tooltip="Special episode"
/>
);
@ -41,7 +46,7 @@ function Legend(props) {
<LegendIconItem
name="Cutoff Not Met"
icon={icons.EPISODE_FILE}
kind={kinds.WARNING}
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
tooltip="Quality or language cutoff has not been met"
/>
);
@ -53,12 +58,16 @@ function Legend(props) {
<LegendItem
status="unaired"
tooltip="Episode hasn't aired yet"
isAgendaView={isAgendaView}
fullColorEvents={fullColorEvents}
colorImpairedMode={colorImpairedMode}
/>
<LegendItem
status="unmonitored"
tooltip="Episode is unmonitored"
isAgendaView={isAgendaView}
fullColorEvents={fullColorEvents}
colorImpairedMode={colorImpairedMode}
/>
</div>
@ -68,12 +77,16 @@ function Legend(props) {
status="onAir"
name="On Air"
tooltip="Episode is currently airing"
isAgendaView={isAgendaView}
fullColorEvents={fullColorEvents}
colorImpairedMode={colorImpairedMode}
/>
<LegendItem
status="missing"
tooltip="Episode has aired and is missing from disk"
isAgendaView={isAgendaView}
fullColorEvents={fullColorEvents}
colorImpairedMode={colorImpairedMode}
/>
</div>
@ -82,12 +95,16 @@ function Legend(props) {
<LegendItem
status="downloading"
tooltip="Episode is currently downloading"
isAgendaView={isAgendaView}
fullColorEvents={fullColorEvents}
colorImpairedMode={colorImpairedMode}
/>
<LegendItem
status="downloaded"
tooltip="Episode was downloaded and sorted"
isAgendaView={isAgendaView}
fullColorEvents={fullColorEvents}
colorImpairedMode={colorImpairedMode}
/>
</div>
@ -97,6 +114,7 @@ function Legend(props) {
name="Premiere"
icon={icons.INFO}
kind={kinds.INFO}
darken={true}
tooltip="Series or season premiere"
/>
@ -115,9 +133,11 @@ function Legend(props) {
}
Legend.propTypes = {
view: PropTypes.string.isRequired,
showFinaleIcon: PropTypes.bool.isRequired,
showSpecialIcon: PropTypes.bool.isRequired,
showCutoffUnmetIcon: PropTypes.bool.isRequired,
fullColorEvents: PropTypes.bool.isRequired,
colorImpairedMode: PropTypes.bool.isRequired
};

View File

@ -6,10 +6,12 @@ import Legend from './Legend';
function createMapStateToProps() {
return createSelector(
(state) => state.calendar.options,
(state) => state.calendar.view,
createUISettingsSelector(),
(calendarOptions, uiSettings) => {
(calendarOptions, view, uiSettings) => {
return {
...calendarOptions,
view,
colorImpairedMode: uiSettings.enableColorImpairedMode
};
}

View File

@ -8,6 +8,7 @@ function LegendIconItem(props) {
name,
icon,
kind,
darken,
tooltip
} = props;
@ -19,6 +20,7 @@ function LegendIconItem(props) {
<Icon
className={styles.icon}
name={icon}
darken={darken}
kind={kind}
/>
@ -31,7 +33,12 @@ LegendIconItem.propTypes = {
name: PropTypes.string.isRequired,
icon: PropTypes.object.isRequired,
kind: PropTypes.string.isRequired,
darken: PropTypes.bool.isRequired,
tooltip: PropTypes.string.isRequired
};
LegendIconItem.defaultProps = {
darken: false
};
export default LegendIconItem;

View File

@ -9,6 +9,8 @@ function LegendItem(props) {
name,
status,
tooltip,
isAgendaView,
fullColorEvents,
colorImpairedMode
} = props;
@ -17,7 +19,8 @@ function LegendItem(props) {
className={classNames(
styles.legendItem,
styles[status],
colorImpairedMode && 'colorImpaired'
colorImpairedMode && 'colorImpaired',
fullColorEvents && !isAgendaView && 'fullColor'
)}
title={tooltip}
>
@ -30,6 +33,8 @@ LegendItem.propTypes = {
name: PropTypes.string,
status: PropTypes.string.isRequired,
tooltip: PropTypes.string.isRequired,
isAgendaView: PropTypes.bool.isRequired,
fullColorEvents: PropTypes.bool.isRequired,
colorImpairedMode: PropTypes.bool.isRequired
};

View File

@ -25,14 +25,16 @@ class CalendarOptionsModalContent extends Component {
firstDayOfWeek,
calendarWeekColumnHeader,
timeFormat,
enableColorImpairedMode
enableColorImpairedMode,
fullColorEvents
} = props;
this.state = {
firstDayOfWeek,
calendarWeekColumnHeader,
timeFormat,
enableColorImpairedMode
enableColorImpairedMode,
fullColorEvents
};
}
@ -96,6 +98,7 @@ class CalendarOptionsModalContent extends Component {
showFinaleIcon,
showSpecialIcon,
showCutoffUnmetIcon,
fullColorEvents,
onModalClose
} = this.props;
@ -174,6 +177,18 @@ class CalendarOptionsModalContent extends Component {
onChange={this.onOptionInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>Full Color Events</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="fullColorEvents"
value={fullColorEvents}
helpText="Altered style to color the entire event with the status color, instead of just the left edge. Does not apply to Agenda"
onChange={this.onOptionInputChange}
/>
</FormGroup>
</Form>
</FieldSet>
@ -214,7 +229,9 @@ class CalendarOptionsModalContent extends Component {
value={timeFormat}
onChange={this.onGlobalInputChange}
/>
</FormGroup><FormGroup>
</FormGroup>
<FormGroup>
<FormLabel>Enable Color-Impaired Mode</FormLabel>
<FormInputGroup
@ -225,7 +242,6 @@ class CalendarOptionsModalContent extends Component {
onChange={this.onGlobalInputChange}
/>
</FormGroup>
</Form>
</FieldSet>
</ModalBody>
@ -250,6 +266,7 @@ CalendarOptionsModalContent.propTypes = {
calendarWeekColumnHeader: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired,
enableColorImpairedMode: PropTypes.bool.isRequired,
fullColorEvents: PropTypes.bool.isRequired,
dispatchSetCalendarOption: PropTypes.func.isRequired,
dispatchSaveUISettings: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired

View File

@ -12,10 +12,18 @@
.info {
color: $infoColor;
&:global(.darken) {
color: color($infoColor shade(30%));
}
}
.pink {
color: $pink;
&:global(.darken) {
color: color($pink shade(30%));
}
}
.success {

View File

@ -18,6 +18,7 @@ class Icon extends PureComponent {
kind,
size,
title,
darken,
isSpinning,
...otherProps
} = this.props;
@ -26,7 +27,8 @@ class Icon extends PureComponent {
<FontAwesomeIcon
className={classNames(
className,
styles[kind]
styles[kind],
darken && 'darken'
)}
icon={name}
spin={isSpinning}
@ -59,6 +61,7 @@ Icon.propTypes = {
kind: PropTypes.string.isRequired,
size: PropTypes.number.isRequired,
title: PropTypes.string,
darken: PropTypes.bool.isRequired,
isSpinning: PropTypes.bool.isRequired,
fixedWidth: PropTypes.bool.isRequired
};
@ -66,6 +69,7 @@ Icon.propTypes = {
Icon.defaultProps = {
kind: kinds.DEFAULT,
size: 14,
darken: false,
isSpinning: false,
fixedWidth: false
};

View File

@ -44,7 +44,8 @@ export const defaultState = {
showEpisodeInformation: true,
showFinaleIcon: false,
showSpecialIcon: false,
showCutoffUnmetIcon: false
showCutoffUnmetIcon: false,
fullColorEvents: false
},
selectedFilterKey: 'monitored',

View File

@ -64,6 +64,10 @@ module.exports = {
inputWarningBoxShadowColor: 'rgba(255, 165, 0, 0.6)',
colorImpairedGradient: '#ffffff',
colorImpairedGradientDark: '#f4f5f6',
colorImpairedDangerGradient: '#d84848',
colorImpairedWarningGradient: '#e59400',
colorImpairedPrimaryGradient: '#538cd4',
colorImpairedGrayGradient: '#9b9b9b ',
//
// Buttons
@ -177,6 +181,7 @@ module.exports = {
calendarTodayBackgroundColor: '#ddd',
calendarBorderColor: '#cecece',
calendarTextDim: '#666',
calendarTextDimAlternate: '#eee',
//
// Table