From 8fff59ff107d9a9fcfc0de1acb6aa635565e5d9b Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Thu, 29 Dec 2022 19:08:14 -0800 Subject: [PATCH] New: Show detailed queue status on Calendar and Series Details Closes #3775 --- frontend/src/Activity/Queue/QueueDetails.css | 5 + frontend/src/Activity/Queue/QueueDetails.js | 120 +++++-------- frontend/src/Activity/Queue/QueueStatus.css | 3 + frontend/src/Activity/Queue/QueueStatus.js | 160 ++++++++++++++++++ .../src/Activity/Queue/QueueStatusCell.js | 129 +------------- frontend/src/Calendar/Agenda/AgendaEvent.css | 23 ++- frontend/src/Calendar/Agenda/AgendaEvent.js | 11 +- .../src/Calendar/Events/CalendarEvent.css | 15 +- frontend/src/Calendar/Events/CalendarEvent.js | 27 +-- .../Events/CalendarEventQueueDetails.js | 17 +- frontend/src/Episode/EpisodeStatus.js | 1 - 11 files changed, 279 insertions(+), 232 deletions(-) create mode 100644 frontend/src/Activity/Queue/QueueDetails.css create mode 100644 frontend/src/Activity/Queue/QueueStatus.css create mode 100644 frontend/src/Activity/Queue/QueueStatus.js diff --git a/frontend/src/Activity/Queue/QueueDetails.css b/frontend/src/Activity/Queue/QueueDetails.css new file mode 100644 index 000000000..b7caba649 --- /dev/null +++ b/frontend/src/Activity/Queue/QueueDetails.css @@ -0,0 +1,5 @@ +.progressBarContainer { + display: flex; + justify-content: center; + width: 100%; +} diff --git a/frontend/src/Activity/Queue/QueueDetails.js b/frontend/src/Activity/Queue/QueueDetails.js index 314aec732..33370f682 100644 --- a/frontend/src/Activity/Queue/QueueDetails.js +++ b/frontend/src/Activity/Queue/QueueDetails.js @@ -1,115 +1,70 @@ -import moment from 'moment'; import PropTypes from 'prop-types'; import React from 'react'; import Icon from 'Components/Icon'; -import { icons, kinds } from 'Helpers/Props'; +import Popover from 'Components/Tooltip/Popover'; +import { icons, tooltipPositions } from 'Helpers/Props'; +import QueueStatus from './QueueStatus'; +import styles from './QueueDetails.css'; function QueueDetails(props) { const { title, size, sizeleft, - estimatedCompletionTime, status, trackedDownloadState, trackedDownloadStatus, + statusMessages, errorMessage, progressBar } = props; const progress = (100 - sizeleft / size * 100); + const isDownloading = status === 'downloading'; + const isPaused = status === 'paused'; + const hasWarning = trackedDownloadStatus === 'warning'; + const hasError = trackedDownloadStatus === 'error'; - if (status === 'pending') { - return ( - - ); - } + if ( + (isDownloading || isPaused) && + !hasWarning && + !hasError + ) { + const state = isPaused ? 'Paused' : 'Downloading'; - if (status === 'completed') { - if (errorMessage) { + if (progress < 5) { return ( ); } - if (trackedDownloadStatus === 'warning') { - return ( - - ); - } - - if (trackedDownloadState === 'importPending') { - return ( - - ); - } - - if (trackedDownloadState === 'importing') { - return ( - - ); - } - } - - if (errorMessage) { return ( - {title} + } + position={tooltipPositions.LEFT} /> ); } - if (status === 'failed') { - return ( - - ); - } - - if (status === 'warning') { - return ( - - ); - } - - if (progress < 5) { - return ( - - ); - } - - return progressBar; + return ( + + ); } QueueDetails.propTypes = { @@ -120,6 +75,7 @@ QueueDetails.propTypes = { status: PropTypes.string.isRequired, trackedDownloadState: PropTypes.string.isRequired, trackedDownloadStatus: PropTypes.string.isRequired, + statusMessages: PropTypes.arrayOf(PropTypes.object), errorMessage: PropTypes.string, progressBar: PropTypes.node.isRequired }; diff --git a/frontend/src/Activity/Queue/QueueStatus.css b/frontend/src/Activity/Queue/QueueStatus.css new file mode 100644 index 000000000..566231656 --- /dev/null +++ b/frontend/src/Activity/Queue/QueueStatus.css @@ -0,0 +1,3 @@ +.noMessages { + margin-bottom: 10px; +} diff --git a/frontend/src/Activity/Queue/QueueStatus.js b/frontend/src/Activity/Queue/QueueStatus.js new file mode 100644 index 000000000..50dd39432 --- /dev/null +++ b/frontend/src/Activity/Queue/QueueStatus.js @@ -0,0 +1,160 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import Icon from 'Components/Icon'; +import Popover from 'Components/Tooltip/Popover'; +import { icons, kinds, tooltipPositions } from 'Helpers/Props'; +import styles from './QueueStatus.css'; + +function getDetailedPopoverBody(statusMessages) { + return ( +
+ { + statusMessages.map(({ title, messages }) => { + return ( +
+ {title} +
    + { + messages.map((message) => { + return ( +
  • + {message} +
  • + ); + }) + } +
+
+ ); + }) + } +
+ ); +} + +function QueueStatus(props) { + const { + sourceTitle, + status, + trackedDownloadStatus, + trackedDownloadState, + statusMessages, + errorMessage, + position, + canFlip + } = props; + + const hasWarning = trackedDownloadStatus === 'warning'; + const hasError = trackedDownloadStatus === 'error'; + + // status === 'downloading' + let iconName = icons.DOWNLOADING; + let iconKind = kinds.DEFAULT; + let title = 'Downloading'; + + if (status === 'paused') { + iconName = icons.PAUSED; + title = 'Paused'; + } + + if (status === 'queued') { + iconName = icons.QUEUED; + title = 'Queued'; + } + + if (status === 'completed') { + iconName = icons.DOWNLOADED; + title = 'Downloaded'; + + if (trackedDownloadState === 'importPending') { + title += ' - Waiting to Import'; + iconKind = kinds.PURPLE; + } + + if (trackedDownloadState === 'importing') { + title += ' - Importing'; + iconKind = kinds.PURPLE; + } + + if (trackedDownloadState === 'failedPending') { + title += ' - Waiting to Process'; + iconKind = kinds.DANGER; + } + } + + if (hasWarning) { + iconKind = kinds.WARNING; + } + + if (status === 'delay') { + iconName = icons.PENDING; + title = 'Pending'; + } + + if (status === 'downloadClientUnavailable') { + iconName = icons.PENDING; + iconKind = kinds.WARNING; + title = 'Pending - Download client is unavailable'; + } + + if (status === 'failed') { + iconName = icons.DOWNLOADING; + iconKind = kinds.DANGER; + title = 'Download failed'; + } + + if (status === 'warning') { + iconName = icons.DOWNLOADING; + iconKind = kinds.WARNING; + title = `Download warning: ${errorMessage || 'check download client for more details'}`; + } + + if (hasError) { + if (status === 'completed') { + iconName = icons.DOWNLOAD; + iconKind = kinds.DANGER; + title = `Import failed: ${sourceTitle}`; + } else { + iconName = icons.DOWNLOADING; + iconKind = kinds.DANGER; + title = 'Download failed'; + } + } + + return ( + + } + title={title} + body={hasWarning || hasError ? getDetailedPopoverBody(statusMessages) : sourceTitle} + position={position} + canFlip={canFlip} + /> + ); +} + +QueueStatus.propTypes = { + sourceTitle: PropTypes.string.isRequired, + status: PropTypes.string.isRequired, + trackedDownloadStatus: PropTypes.string.isRequired, + trackedDownloadState: PropTypes.string.isRequired, + statusMessages: PropTypes.arrayOf(PropTypes.object), + errorMessage: PropTypes.string, + position: PropTypes.oneOf(tooltipPositions.all).isRequired, + canFlip: PropTypes.bool.isRequired +}; + +QueueStatus.defaultProps = { + trackedDownloadStatus: 'Ok', + trackedDownloadState: 'Downloading', + canFlip: false +}; + +export default QueueStatus; diff --git a/frontend/src/Activity/Queue/QueueStatusCell.js b/frontend/src/Activity/Queue/QueueStatusCell.js index 6c8e93696..adbccda13 100644 --- a/frontend/src/Activity/Queue/QueueStatusCell.js +++ b/frontend/src/Activity/Queue/QueueStatusCell.js @@ -1,41 +1,10 @@ import PropTypes from 'prop-types'; import React from 'react'; -import Icon from 'Components/Icon'; import TableRowCell from 'Components/Table/Cells/TableRowCell'; -import Popover from 'Components/Tooltip/Popover'; -import { icons, kinds, tooltipPositions } from 'Helpers/Props'; +import { tooltipPositions } from 'Helpers/Props'; +import QueueStatus from './QueueStatus'; import styles from './QueueStatusCell.css'; -function getDetailedPopoverBody(statusMessages) { - return ( -
- { - statusMessages.map(({ title, messages }) => { - return ( -
- {title} -
    - { - messages.map((message) => { - return ( -
  • - {message} -
  • - ); - }) - } -
-
- ); - }) - } -
- ); -} - function QueueStatusCell(props) { const { sourceTitle, @@ -46,96 +15,16 @@ function QueueStatusCell(props) { errorMessage } = props; - const hasWarning = trackedDownloadStatus === 'warning'; - const hasError = trackedDownloadStatus === 'error'; - - // status === 'downloading' - let iconName = icons.DOWNLOADING; - let iconKind = kinds.DEFAULT; - let title = 'Downloading'; - - if (status === 'paused') { - iconName = icons.PAUSED; - title = 'Paused'; - } - - if (status === 'queued') { - iconName = icons.QUEUED; - title = 'Queued'; - } - - if (status === 'completed') { - iconName = icons.DOWNLOADED; - title = 'Downloaded'; - - if (trackedDownloadState === 'importPending') { - title += ' - Waiting to Import'; - iconKind = kinds.PURPLE; - } - - if (trackedDownloadState === 'importing') { - title += ' - Importing'; - iconKind = kinds.PURPLE; - } - - if (trackedDownloadState === 'failedPending') { - title += ' - Waiting to Process'; - iconKind = kinds.DANGER; - } - } - - if (hasWarning) { - iconKind = kinds.WARNING; - } - - if (status === 'delay') { - iconName = icons.PENDING; - title = 'Pending'; - } - - if (status === 'downloadClientUnavailable') { - iconName = icons.PENDING; - iconKind = kinds.WARNING; - title = 'Pending - Download client is unavailable'; - } - - if (status === 'failed') { - iconName = icons.DOWNLOADING; - iconKind = kinds.DANGER; - title = 'Download failed'; - } - - if (status === 'warning') { - iconName = icons.DOWNLOADING; - iconKind = kinds.WARNING; - title = `Download warning: ${errorMessage || 'check download client for more details'}`; - } - - if (hasError) { - if (status === 'completed') { - iconName = icons.DOWNLOAD; - iconKind = kinds.DANGER; - title = `Import failed: ${sourceTitle}`; - } else { - iconName = icons.DOWNLOADING; - iconKind = kinds.DANGER; - title = 'Download failed'; - } - } - return ( - - } - title={title} - body={hasWarning || hasError ? getDetailedPopoverBody(statusMessages) : sourceTitle} + ); diff --git a/frontend/src/Calendar/Agenda/AgendaEvent.css b/frontend/src/Calendar/Agenda/AgendaEvent.css index a6bc8dd83..27b91b857 100644 --- a/frontend/src/Calendar/Agenda/AgendaEvent.css +++ b/frontend/src/Calendar/Agenda/AgendaEvent.css @@ -1,15 +1,30 @@ .event { - display: flex; - overflow-x: hidden; + position: relative; padding: 5px; border-bottom: 1px solid var(--borderColor); - font-size: $defaultFontSize; +} + +.underlay { + @add-mixin cover; &:hover { background-color: var(--tableRowHoverBackgroundColor); } } +.overlay { + @add-mixin linkOverlay; + + position: relative; + display: flex; + overflow-x: hidden; + font-size: $defaultFontSize; + + &:global(.colorImpaired) { + border-left-width: 5px; + } +} + .eventWrapper { display: flex; flex: 1 0 1px; @@ -56,6 +71,8 @@ .statusIcon { margin-left: 3px; + cursor: default; + pointer-events: all; } /* diff --git a/frontend/src/Calendar/Agenda/AgendaEvent.js b/frontend/src/Calendar/Agenda/AgendaEvent.js index bb3a331ad..155be19f1 100644 --- a/frontend/src/Calendar/Agenda/AgendaEvent.js +++ b/frontend/src/Calendar/Agenda/AgendaEvent.js @@ -74,12 +74,13 @@ class AgendaEvent extends Component { const seasonStatistics = season?.statistics || {}; return ( -
+
+ /> + +
{ showDate && @@ -209,7 +210,7 @@ class AgendaEvent extends Component { /> }
- +
+
+ /> + +
{series.title} @@ -215,7 +218,7 @@ class CalendarEvent extends Component {
{formatTime(airDateUtc, timeFormat)} - {formatTime(endTime.toISOString(), timeFormat, { includeMinuteZero: true })}
- +
- +
); } } diff --git a/frontend/src/Calendar/Events/CalendarEventQueueDetails.js b/frontend/src/Calendar/Events/CalendarEventQueueDetails.js index 60282d7ed..db26eb1d2 100644 --- a/frontend/src/Calendar/Events/CalendarEventQueueDetails.js +++ b/frontend/src/Calendar/Events/CalendarEventQueueDetails.js @@ -12,6 +12,7 @@ function CalendarEventQueueDetails(props) { status, trackedDownloadState, trackedDownloadStatus, + statusMessages, errorMessage } = props; @@ -26,16 +27,15 @@ function CalendarEventQueueDetails(props) { status={status} trackedDownloadState={trackedDownloadState} trackedDownloadStatus={trackedDownloadStatus} + statusMessages={statusMessages} errorMessage={errorMessage} progressBar={ -
- -
+ } /> ); @@ -49,6 +49,7 @@ CalendarEventQueueDetails.propTypes = { status: PropTypes.string.isRequired, trackedDownloadState: PropTypes.string.isRequired, trackedDownloadStatus: PropTypes.string.isRequired, + statusMessages: PropTypes.arrayOf(PropTypes.object), errorMessage: PropTypes.string }; diff --git a/frontend/src/Episode/EpisodeStatus.js b/frontend/src/Episode/EpisodeStatus.js index 5a5eca125..7b93f6127 100644 --- a/frontend/src/Episode/EpisodeStatus.js +++ b/frontend/src/Episode/EpisodeStatus.js @@ -35,7 +35,6 @@ function EpisodeStatus(props) { {...queueItem} progressBar={