mirror of
https://github.com/lidarr/lidarr.git
synced 2025-07-30 03:38:26 -07:00
[UI Work] Add Artist, Import Artist, Calendar
This commit is contained in:
parent
a747c5f135
commit
77f1d2e64c
109 changed files with 891 additions and 1082 deletions
|
@ -13,14 +13,14 @@ function Agenda(props) {
|
|||
<div className={styles.agenda}>
|
||||
{
|
||||
items.map((item, index) => {
|
||||
const momentDate = moment(item.airDateUtc);
|
||||
const momentDate = moment(item.releaseDate);
|
||||
const showDate = index === 0 ||
|
||||
!moment(items[index - 1].airDateUtc).isSame(momentDate, 'day');
|
||||
!moment(items[index - 1].releaseDate).isSame(momentDate, 'day');
|
||||
|
||||
return (
|
||||
<AgendaEventConnector
|
||||
key={item.id}
|
||||
episodeId={item.id}
|
||||
albumId={item.id}
|
||||
showDate={showDate}
|
||||
{...item}
|
||||
/>
|
||||
|
|
|
@ -26,15 +26,15 @@
|
|||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.seriesTitle,
|
||||
.episodeTitle {
|
||||
.artistName,
|
||||
.albumTitle {
|
||||
composes: truncate from 'Styles/Mixins/truncate.css';
|
||||
|
||||
flex: 0 1 300px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.episodeTitle {
|
||||
.albumTitle {
|
||||
flex: 1 1 1px;
|
||||
}
|
||||
|
||||
|
@ -66,18 +66,10 @@
|
|||
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';
|
||||
}
|
||||
|
@ -98,7 +90,7 @@
|
|||
|
||||
.date,
|
||||
.time,
|
||||
.seriesTitle {
|
||||
.artistName {
|
||||
flex: 0 0 100%;
|
||||
}
|
||||
|
||||
|
|
|
@ -42,12 +42,12 @@ class AgendaEvent extends Component {
|
|||
render() {
|
||||
const {
|
||||
id,
|
||||
series,
|
||||
artist,
|
||||
title,
|
||||
seasonNumber,
|
||||
episodeNumber,
|
||||
absoluteEpisodeNumber,
|
||||
airDateUtc,
|
||||
// seasonNumber,
|
||||
// episodeNumber,
|
||||
// absoluteEpisodeNumber,
|
||||
releaseDate,
|
||||
monitored,
|
||||
hasFile,
|
||||
grabbed,
|
||||
|
@ -57,11 +57,11 @@ class AgendaEvent extends Component {
|
|||
longDateFormat
|
||||
} = this.props;
|
||||
|
||||
const startTime = moment(airDateUtc);
|
||||
const endTime = startTime.add(series.runtime, 'minutes');
|
||||
const startTime = moment(releaseDate);
|
||||
// const endTime = startTime.add(artist.runtime, 'minutes');
|
||||
const downloading = !!(queueItem || grabbed);
|
||||
const isMonitored = series.monitored && monitored;
|
||||
const statusStyle = getStatusStyle(episodeNumber, hasFile, downloading, startTime, endTime, isMonitored);
|
||||
const isMonitored = artist.monitored && monitored;
|
||||
const statusStyle = getStatusStyle(id, hasFile, downloading, startTime, isMonitored);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -85,34 +85,25 @@ class AgendaEvent extends Component {
|
|||
/>
|
||||
|
||||
<div className={styles.time}>
|
||||
{formatTime(airDateUtc, timeFormat)} - {formatTime(endTime.toISOString(), timeFormat, { includeMinuteZero: true })}
|
||||
{formatTime(releaseDate, timeFormat)}
|
||||
</div>
|
||||
|
||||
<div className={styles.seriesTitle}>
|
||||
{series.title}
|
||||
<div className={styles.artistName}>
|
||||
{artist.artistName}
|
||||
</div>
|
||||
|
||||
<div className={styles.seasonEpisodeNumber}>
|
||||
{seasonNumber}x{padNumber(episodeNumber, 2)}
|
||||
<div className={styles.episodeSeparator}> - </div>
|
||||
|
||||
{
|
||||
series.seriesType === 'anime' && absoluteEpisodeNumber &&
|
||||
<span className={styles.absoluteEpisodeNumber}>({absoluteEpisodeNumber})</span>
|
||||
}
|
||||
|
||||
<div className={styles.episodeSeparator}> - </div>
|
||||
</div>
|
||||
|
||||
<div className={styles.episodeTitle}>
|
||||
<div className={styles.albumTitle}>
|
||||
{title}
|
||||
</div>
|
||||
|
||||
{
|
||||
!!queueItem &&
|
||||
<CalendarEventQueueDetails
|
||||
seriesType={series.seriesType}
|
||||
seasonNumber={seasonNumber}
|
||||
absoluteEpisodeNumber={absoluteEpisodeNumber}
|
||||
seriesType={artist.seriesType}
|
||||
// seasonNumber={seasonNumber}
|
||||
// absoluteEpisodeNumber={absoluteEpisodeNumber}
|
||||
{...queueItem}
|
||||
/>
|
||||
}
|
||||
|
@ -121,7 +112,7 @@ class AgendaEvent extends Component {
|
|||
!queueItem && grabbed &&
|
||||
<Icon
|
||||
name={icons.DOWNLOADING}
|
||||
title="Episode is downloading"
|
||||
title="Album is downloading"
|
||||
/>
|
||||
}
|
||||
</Link>
|
||||
|
@ -130,7 +121,7 @@ class AgendaEvent extends Component {
|
|||
isOpen={this.state.isDetailsModalOpen}
|
||||
episodeId={id}
|
||||
episodeEntity={episodeEntities.CALENDAR}
|
||||
artistId={series.id}
|
||||
artistId={artist.id}
|
||||
episodeTitle={title}
|
||||
showOpenSeriesButton={true}
|
||||
onModalClose={this.onDetailsModalClose}
|
||||
|
@ -142,12 +133,12 @@ class AgendaEvent extends Component {
|
|||
|
||||
AgendaEvent.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
series: PropTypes.object.isRequired,
|
||||
artist: PropTypes.object.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
seasonNumber: PropTypes.number.isRequired,
|
||||
episodeNumber: PropTypes.number.isRequired,
|
||||
absoluteEpisodeNumber: PropTypes.number,
|
||||
airDateUtc: PropTypes.string.isRequired,
|
||||
// seasonNumber: PropTypes.number.isRequired,
|
||||
// episodeNumber: PropTypes.number.isRequired,
|
||||
// absoluteEpisodeNumber: PropTypes.number,
|
||||
releaseDate: PropTypes.string.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
hasFile: PropTypes.bool.isRequired,
|
||||
grabbed: PropTypes.bool,
|
||||
|
|
|
@ -10,9 +10,9 @@ function createMapStateToProps() {
|
|||
createArtistSelector(),
|
||||
createQueueItemSelector(),
|
||||
createUISettingsSelector(),
|
||||
(series, queueItem, uiSettings) => {
|
||||
(artist, queueItem, uiSettings) => {
|
||||
return {
|
||||
series,
|
||||
artist,
|
||||
queueItem,
|
||||
timeFormat: uiSettings.timeFormat,
|
||||
longDateFormat: uiSettings.longDateFormat
|
||||
|
|
|
@ -12,10 +12,10 @@ function createCalendarEventsConnector() {
|
|||
(state) => state.calendar,
|
||||
(date, calendar) => {
|
||||
const filtered = _.filter(calendar.items, (item) => {
|
||||
return moment(date).isSame(moment(item.airDateUtc), 'day');
|
||||
return moment(date).isSame(moment(item.releaseDate), 'day');
|
||||
});
|
||||
|
||||
return _.sortBy(filtered, (item) => moment(item.airDateUtc).unix());
|
||||
return _.sortBy(filtered, (item) => moment(item.releaseDate).unix());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,19 +8,19 @@
|
|||
}
|
||||
|
||||
.info,
|
||||
.episodeInfo {
|
||||
.albumInfo {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.seriesTitle,
|
||||
.episodeTitle {
|
||||
.artistName,
|
||||
.albumTitle {
|
||||
composes: truncate from 'Styles/Mixins/truncate.css';
|
||||
|
||||
flex: 1 0 1px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.seriesTitle {
|
||||
.artistName {
|
||||
color: #3a3f51;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
@ -53,14 +53,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.onAir {
|
||||
border-left-color: $warningColor;
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(90deg, transparent, transparent 5px, #eee 5px, #eee 10px);
|
||||
}
|
||||
}
|
||||
|
||||
.missing {
|
||||
border-left-color: $dangerColor;
|
||||
|
||||
|
@ -69,14 +61,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.premiere {
|
||||
border-left-color: $sonarrBlue;
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(90deg, transparent, transparent 5px, #eee 5px, #eee 10px);
|
||||
}
|
||||
}
|
||||
|
||||
.unaired {
|
||||
border-left-color: $primaryColor;
|
||||
|
||||
|
|
|
@ -47,26 +47,26 @@ class CalendarEvent extends Component {
|
|||
render() {
|
||||
const {
|
||||
id,
|
||||
series,
|
||||
artist,
|
||||
title,
|
||||
seasonNumber,
|
||||
episodeNumber,
|
||||
absoluteEpisodeNumber,
|
||||
airDateUtc,
|
||||
// seasonNumber,
|
||||
// episodeNumber,
|
||||
// absoluteEpisodeNumber,
|
||||
releaseDate,
|
||||
monitored,
|
||||
hasFile,
|
||||
grabbed,
|
||||
queueItem,
|
||||
timeFormat,
|
||||
// timeFormat,
|
||||
colorImpairedMode
|
||||
} = this.props;
|
||||
|
||||
const startTime = moment(airDateUtc);
|
||||
const endTime = startTime.add(series.runtime, 'minutes');
|
||||
const startTime = moment(releaseDate);
|
||||
// const endTime = startTime.add(artist.runtime, 'minutes');
|
||||
const downloading = !!(queueItem || grabbed);
|
||||
const isMonitored = series.monitored && monitored;
|
||||
const statusStyle = getStatusStyle(episodeNumber, hasFile, downloading, startTime, endTime, isMonitored);
|
||||
const missingAbsoluteNumber = series.seriesType === 'anime' && seasonNumber > 0 && !absoluteEpisodeNumber;
|
||||
const isMonitored = artist.monitored && monitored;
|
||||
const statusStyle = getStatusStyle(id, hasFile, downloading, startTime, isMonitored);
|
||||
// const missingAbsoluteNumber = artist.seriesType === 'anime' && seasonNumber > 0 && !absoluteEpisodeNumber;
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -80,18 +80,10 @@ class CalendarEvent extends Component {
|
|||
onPress={this.onPress}
|
||||
>
|
||||
<div className={styles.info}>
|
||||
<div className={styles.seriesTitle}>
|
||||
{series.title}
|
||||
<div className={styles.artistName}>
|
||||
{artist.artistName}
|
||||
</div>
|
||||
|
||||
{
|
||||
missingAbsoluteNumber &&
|
||||
<Icon
|
||||
name={icons.WARNING}
|
||||
title="Episode does not have an absolute episode number"
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
!!queueItem &&
|
||||
<span className={styles.statusIcon}>
|
||||
|
@ -106,28 +98,15 @@ class CalendarEvent extends Component {
|
|||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={icons.DOWNLOADING}
|
||||
title="Episode is downloading"
|
||||
title="Album is downloading"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className={styles.episodeInfo}>
|
||||
<div className={styles.episodeTitle}>
|
||||
<div className={styles.albumInfo}>
|
||||
<div className={styles.albumTitle}>
|
||||
{title}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{seasonNumber}x{padNumber(episodeNumber, 2)}
|
||||
|
||||
{
|
||||
series.seriesType === 'anime' && absoluteEpisodeNumber &&
|
||||
<span className={styles.absoluteEpisodeNumber}>({absoluteEpisodeNumber})</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{formatTime(airDateUtc, timeFormat)} - {formatTime(endTime.toISOString(), timeFormat, { includeMinuteZero: true })}
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
|
@ -135,7 +114,7 @@ class CalendarEvent extends Component {
|
|||
isOpen={this.state.isDetailsModalOpen}
|
||||
episodeId={id}
|
||||
episodeEntity={episodeEntities.CALENDAR}
|
||||
artistId={series.id}
|
||||
artistId={artist.id}
|
||||
episodeTitle={title}
|
||||
showOpenSeriesButton={true}
|
||||
onModalClose={this.onDetailsModalClose}
|
||||
|
@ -147,17 +126,17 @@ class CalendarEvent extends Component {
|
|||
|
||||
CalendarEvent.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
series: PropTypes.object.isRequired,
|
||||
artist: PropTypes.object.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
seasonNumber: PropTypes.number.isRequired,
|
||||
episodeNumber: PropTypes.number.isRequired,
|
||||
absoluteEpisodeNumber: PropTypes.number,
|
||||
airDateUtc: PropTypes.string.isRequired,
|
||||
// seasonNumber: PropTypes.number.isRequired,
|
||||
// episodeNumber: PropTypes.number.isRequired,
|
||||
// absoluteEpisodeNumber: PropTypes.number,
|
||||
releaseDate: PropTypes.string.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
hasFile: PropTypes.bool.isRequired,
|
||||
grabbed: PropTypes.bool,
|
||||
queueItem: PropTypes.object,
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
// timeFormat: PropTypes.string.isRequired,
|
||||
colorImpairedMode: PropTypes.bool.isRequired,
|
||||
onEventModalOpenToggle: PropTypes.func.isRequired
|
||||
};
|
||||
|
|
|
@ -10,9 +10,9 @@ function createMapStateToProps() {
|
|||
createArtistSelector(),
|
||||
createQueueItemSelector(),
|
||||
createUISettingsSelector(),
|
||||
(series, queueItem, uiSettings) => {
|
||||
(artist, queueItem, uiSettings) => {
|
||||
return {
|
||||
series,
|
||||
artist,
|
||||
queueItem,
|
||||
timeFormat: uiSettings.timeFormat,
|
||||
colorImpairedMode: uiSettings.enableColorImpairedMode
|
||||
|
|
|
@ -6,46 +6,30 @@ import styles from './Legend.css';
|
|||
function Legend({ colorImpairedMode }) {
|
||||
return (
|
||||
<div className={styles.legend}>
|
||||
<div>
|
||||
<LegendItem
|
||||
name="Unaired Premiere"
|
||||
status="premiere"
|
||||
tooltip="Premiere episode hasn't aired yet"
|
||||
colorImpairedMode={colorImpairedMode}
|
||||
/>
|
||||
|
||||
<LegendItem
|
||||
status="unaired"
|
||||
tooltip="Episode hasn't aired yet"
|
||||
colorImpairedMode={colorImpairedMode}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<LegendItem
|
||||
status="downloading"
|
||||
tooltip="Episode is currently downloading"
|
||||
tooltip="Album is currently downloading"
|
||||
colorImpairedMode={colorImpairedMode}
|
||||
/>
|
||||
|
||||
<LegendItem
|
||||
status="downloaded"
|
||||
tooltip="Episode was downloaded and sorted"
|
||||
tooltip="Album was downloaded and sorted"
|
||||
colorImpairedMode={colorImpairedMode}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<LegendItem
|
||||
name="On Air"
|
||||
status="onAir"
|
||||
tooltip="Episode is currently airing"
|
||||
status="unaired"
|
||||
tooltip="Album hasn't released yet"
|
||||
colorImpairedMode={colorImpairedMode}
|
||||
/>
|
||||
|
||||
<LegendItem
|
||||
status="missing"
|
||||
tooltip="Episode file has not been found"
|
||||
tooltip="Track file has not been found"
|
||||
colorImpairedMode={colorImpairedMode}
|
||||
/>
|
||||
</div>
|
||||
|
@ -53,7 +37,7 @@ function Legend({ colorImpairedMode }) {
|
|||
<div>
|
||||
<LegendItem
|
||||
status="unmonitored"
|
||||
tooltip="Episode is unmonitored"
|
||||
tooltip="Album is unmonitored"
|
||||
colorImpairedMode={colorImpairedMode}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -32,10 +32,6 @@
|
|||
composes: missing from 'Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
.premiere {
|
||||
composes: premiere from 'Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
.unaired {
|
||||
composes: unaired from 'Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import moment from 'moment';
|
||||
|
||||
function getStatusStyle(episodeNumber, hasFile, downloading, startTime, endTime, isMonitored) {
|
||||
function getStatusStyle(episodeNumber, hasFile, downloading, startTime, isMonitored) {
|
||||
const currentTime = moment();
|
||||
|
||||
if (hasFile) {
|
||||
|
@ -15,18 +15,10 @@ function getStatusStyle(episodeNumber, hasFile, downloading, startTime, endTime,
|
|||
return 'unmonitored';
|
||||
}
|
||||
|
||||
if (currentTime.isAfter(startTime) && currentTime.isBefore(endTime)) {
|
||||
return 'onAir';
|
||||
}
|
||||
|
||||
if (endTime.isBefore(currentTime) && !hasFile) {
|
||||
if (currentTime.isAfter(startTime)) {
|
||||
return 'missing';
|
||||
}
|
||||
|
||||
if (episodeNumber === 1) {
|
||||
return 'premiere';
|
||||
}
|
||||
|
||||
return 'unaired';
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ function getUrls(state) {
|
|||
tags
|
||||
} = state;
|
||||
|
||||
let icalUrl = `${window.location.host}${window.Sonarr.urlBase}/feed/calendar/Sonarr.ics?`;
|
||||
let icalUrl = `${window.location.host}${window.Sonarr.urlBase}/feed/calendar/Lidarr.ics?`;
|
||||
|
||||
if (unmonitored) {
|
||||
icalUrl += 'unmonitored=true&';
|
||||
|
@ -115,7 +115,7 @@ class CalendarLinkModalContent extends Component {
|
|||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
Sonarr Calendar Feed
|
||||
Lidarr Calendar Feed
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
|
@ -127,7 +127,7 @@ class CalendarLinkModalContent extends Component {
|
|||
type={inputTypes.CHECK}
|
||||
name="unmonitored"
|
||||
value={unmonitored}
|
||||
helpText="Include unmonitored episodes in the iCal feed"
|
||||
helpText="Include unmonitored albums in the iCal feed"
|
||||
onChange={this.onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
@ -139,7 +139,7 @@ class CalendarLinkModalContent extends Component {
|
|||
type={inputTypes.CHECK}
|
||||
name="premieresOnly"
|
||||
value={premieresOnly}
|
||||
helpText="Only the first episode in a season will be in the feed"
|
||||
helpText="Only the first album from an artist will be in the feed"
|
||||
onChange={this.onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
@ -163,7 +163,7 @@ class CalendarLinkModalContent extends Component {
|
|||
type={inputTypes.TAG}
|
||||
name="tags"
|
||||
value={tags}
|
||||
helpText="Feed will only contain series with at least one matching tag"
|
||||
helpText="Feed will only contain artists with at least one matching tag"
|
||||
onChange={this.onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue