mirror of
https://github.com/Sonarr/Sonarr
synced 2025-12-07 08:53:14 +01:00
Convert EpisodeHistory to TypeScript
This commit is contained in:
parent
03b8c4c28e
commit
f1d54d2a9a
8 changed files with 283 additions and 375 deletions
|
|
@ -70,6 +70,7 @@ interface AppState {
|
||||||
captcha: CaptchaAppState;
|
captcha: CaptchaAppState;
|
||||||
commands: CommandAppState;
|
commands: CommandAppState;
|
||||||
episodeFiles: EpisodeFilesAppState;
|
episodeFiles: EpisodeFilesAppState;
|
||||||
|
episodeHistory: HistoryAppState;
|
||||||
episodes: EpisodesAppState;
|
episodes: EpisodesAppState;
|
||||||
episodesSelection: EpisodesAppState;
|
episodesSelection: EpisodesAppState;
|
||||||
history: HistoryAppState;
|
history: HistoryAppState;
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import {
|
||||||
clearReleases,
|
clearReleases,
|
||||||
} from 'Store/Actions/releaseActions';
|
} from 'Store/Actions/releaseActions';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import EpisodeHistoryConnector from './History/EpisodeHistoryConnector';
|
import EpisodeHistory from './History/EpisodeHistory';
|
||||||
import EpisodeSearch from './Search/EpisodeSearch';
|
import EpisodeSearch from './Search/EpisodeSearch';
|
||||||
import SeasonEpisodeNumber from './SeasonEpisodeNumber';
|
import SeasonEpisodeNumber from './SeasonEpisodeNumber';
|
||||||
import EpisodeSummary from './Summary/EpisodeSummary';
|
import EpisodeSummary from './Summary/EpisodeSummary';
|
||||||
|
|
@ -168,7 +168,7 @@ function EpisodeDetailsModalContent(props: EpisodeDetailsModalContentProps) {
|
||||||
|
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<div className={styles.tabContent}>
|
<div className={styles.tabContent}>
|
||||||
<EpisodeHistoryConnector episodeId={episodeId} />
|
<EpisodeHistory episodeId={episodeId} />
|
||||||
</div>
|
</div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import Alert from 'Components/Alert';
|
|
||||||
import Icon from 'Components/Icon';
|
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
|
||||||
import Table from 'Components/Table/Table';
|
|
||||||
import TableBody from 'Components/Table/TableBody';
|
|
||||||
import { icons, kinds } from 'Helpers/Props';
|
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
import EpisodeHistoryRow from './EpisodeHistoryRow';
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
name: 'eventType',
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'sourceTitle',
|
|
||||||
label: () => translate('SourceTitle'),
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'languages',
|
|
||||||
label: () => translate('Languages'),
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'quality',
|
|
||||||
label: () => translate('Quality'),
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'customFormats',
|
|
||||||
label: () => translate('CustomFormats'),
|
|
||||||
isSortable: false,
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'customFormatScore',
|
|
||||||
label: React.createElement(Icon, {
|
|
||||||
name: icons.SCORE,
|
|
||||||
title: () => translate('CustomFormatScore')
|
|
||||||
}),
|
|
||||||
isSortable: true,
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'date',
|
|
||||||
label: () => translate('Date'),
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'actions',
|
|
||||||
isVisible: true
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
class EpisodeHistory extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
isFetching,
|
|
||||||
isPopulated,
|
|
||||||
error,
|
|
||||||
items,
|
|
||||||
onMarkAsFailedPress
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const hasItems = !!items.length;
|
|
||||||
|
|
||||||
if (isFetching) {
|
|
||||||
return (
|
|
||||||
<LoadingIndicator />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isFetching && !!error) {
|
|
||||||
return (
|
|
||||||
<Alert kind={kinds.DANGER}>{translate('EpisodeHistoryLoadError')}</Alert>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPopulated && !hasItems && !error) {
|
|
||||||
return (
|
|
||||||
<Alert kind={kinds.INFO}>{translate('NoEpisodeHistory')}</Alert>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPopulated && hasItems && !error) {
|
|
||||||
return (
|
|
||||||
<Table
|
|
||||||
columns={columns}
|
|
||||||
>
|
|
||||||
<TableBody>
|
|
||||||
{
|
|
||||||
items.map((item) => {
|
|
||||||
return (
|
|
||||||
<EpisodeHistoryRow
|
|
||||||
key={item.id}
|
|
||||||
{...item}
|
|
||||||
onMarkAsFailedPress={onMarkAsFailedPress}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
EpisodeHistory.propTypes = {
|
|
||||||
isFetching: PropTypes.bool.isRequired,
|
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
|
||||||
error: PropTypes.object,
|
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
onMarkAsFailedPress: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
EpisodeHistory.defaultProps = {
|
|
||||||
selectedTab: 'details'
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EpisodeHistory;
|
|
||||||
129
frontend/src/Episode/History/EpisodeHistory.tsx
Normal file
129
frontend/src/Episode/History/EpisodeHistory.tsx
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
import React, { useCallback, useEffect } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import AppState from 'App/State/AppState';
|
||||||
|
import Alert from 'Components/Alert';
|
||||||
|
import Icon from 'Components/Icon';
|
||||||
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import Column from 'Components/Table/Column';
|
||||||
|
import Table from 'Components/Table/Table';
|
||||||
|
import TableBody from 'Components/Table/TableBody';
|
||||||
|
import { icons, kinds } from 'Helpers/Props';
|
||||||
|
import {
|
||||||
|
clearEpisodeHistory,
|
||||||
|
episodeHistoryMarkAsFailed,
|
||||||
|
fetchEpisodeHistory,
|
||||||
|
} from 'Store/Actions/episodeHistoryActions';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import EpisodeHistoryRow from './EpisodeHistoryRow';
|
||||||
|
|
||||||
|
const columns: Column[] = [
|
||||||
|
{
|
||||||
|
name: 'eventType',
|
||||||
|
label: '',
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sourceTitle',
|
||||||
|
label: () => translate('SourceTitle'),
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'languages',
|
||||||
|
label: () => translate('Languages'),
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'quality',
|
||||||
|
label: () => translate('Quality'),
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'customFormats',
|
||||||
|
label: () => translate('CustomFormats'),
|
||||||
|
isSortable: false,
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'customFormatScore',
|
||||||
|
label: React.createElement(Icon, {
|
||||||
|
name: icons.SCORE,
|
||||||
|
title: () => translate('CustomFormatScore'),
|
||||||
|
}),
|
||||||
|
isSortable: true,
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'date',
|
||||||
|
label: () => translate('Date'),
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'actions',
|
||||||
|
label: '',
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
interface EpisodeHistoryProps {
|
||||||
|
episodeId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function EpisodeHistory({ episodeId }: EpisodeHistoryProps) {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { items, isFetching, isPopulated, error } = useSelector(
|
||||||
|
(state: AppState) => state.episodeHistory
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleMarkAsFailedPress = useCallback(
|
||||||
|
(historyId: number) => {
|
||||||
|
dispatch(episodeHistoryMarkAsFailed({ historyId, episodeId }));
|
||||||
|
},
|
||||||
|
[episodeId, dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasItems = !!items.length;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(fetchEpisodeHistory({ episodeId }));
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
dispatch(clearEpisodeHistory());
|
||||||
|
};
|
||||||
|
}, [episodeId, dispatch]);
|
||||||
|
|
||||||
|
if (isFetching) {
|
||||||
|
return <LoadingIndicator />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isFetching && !!error) {
|
||||||
|
return (
|
||||||
|
<Alert kind={kinds.DANGER}>{translate('EpisodeHistoryLoadError')}</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPopulated && !hasItems && !error) {
|
||||||
|
return <Alert kind={kinds.INFO}>{translate('NoEpisodeHistory')}</Alert>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPopulated && hasItems && !error) {
|
||||||
|
return (
|
||||||
|
<Table columns={columns}>
|
||||||
|
<TableBody>
|
||||||
|
{items.map((item) => {
|
||||||
|
return (
|
||||||
|
<EpisodeHistoryRow
|
||||||
|
key={item.id}
|
||||||
|
{...item}
|
||||||
|
onMarkAsFailedPress={handleMarkAsFailedPress}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EpisodeHistory;
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import { clearEpisodeHistory, episodeHistoryMarkAsFailed, fetchEpisodeHistory } from 'Store/Actions/episodeHistoryActions';
|
|
||||||
import EpisodeHistory from './EpisodeHistory';
|
|
||||||
|
|
||||||
function createMapStateToProps() {
|
|
||||||
return createSelector(
|
|
||||||
(state) => state.episodeHistory,
|
|
||||||
(episodeHistory) => {
|
|
||||||
return episodeHistory;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
|
||||||
fetchEpisodeHistory,
|
|
||||||
clearEpisodeHistory,
|
|
||||||
episodeHistoryMarkAsFailed
|
|
||||||
};
|
|
||||||
|
|
||||||
class EpisodeHistoryConnector extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.props.fetchEpisodeHistory({ episodeId: this.props.episodeId });
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.props.clearEpisodeHistory();
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onMarkAsFailedPress = (historyId) => {
|
|
||||||
this.props.episodeHistoryMarkAsFailed({ historyId, episodeId: this.props.episodeId });
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<EpisodeHistory
|
|
||||||
{...this.props}
|
|
||||||
onMarkAsFailedPress={this.onMarkAsFailedPress}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
EpisodeHistoryConnector.propTypes = {
|
|
||||||
episodeId: PropTypes.number.isRequired,
|
|
||||||
fetchEpisodeHistory: PropTypes.func.isRequired,
|
|
||||||
clearEpisodeHistory: PropTypes.func.isRequired,
|
|
||||||
episodeHistoryMarkAsFailed: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps)(EpisodeHistoryConnector);
|
|
||||||
|
|
@ -1,177 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import HistoryDetails from 'Activity/History/Details/HistoryDetails';
|
|
||||||
import HistoryEventTypeCell from 'Activity/History/HistoryEventTypeCell';
|
|
||||||
import Icon from 'Components/Icon';
|
|
||||||
import IconButton from 'Components/Link/IconButton';
|
|
||||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
|
||||||
import RelativeDateCell from 'Components/Table/Cells/RelativeDateCell';
|
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
|
||||||
import TableRow from 'Components/Table/TableRow';
|
|
||||||
import Popover from 'Components/Tooltip/Popover';
|
|
||||||
import EpisodeFormats from 'Episode/EpisodeFormats';
|
|
||||||
import EpisodeLanguages from 'Episode/EpisodeLanguages';
|
|
||||||
import EpisodeQuality from 'Episode/EpisodeQuality';
|
|
||||||
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
|
||||||
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
|
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
import styles from './EpisodeHistoryRow.css';
|
|
||||||
|
|
||||||
function getTitle(eventType) {
|
|
||||||
switch (eventType) {
|
|
||||||
case 'grabbed': return 'Grabbed';
|
|
||||||
case 'seriesFolderImported': return 'Series Folder Imported';
|
|
||||||
case 'downloadFolderImported': return 'Download Folder Imported';
|
|
||||||
case 'downloadFailed': return 'Download Failed';
|
|
||||||
case 'episodeFileDeleted': return 'Episode File Deleted';
|
|
||||||
case 'episodeFileRenamed': return 'Episode File Renamed';
|
|
||||||
default: return 'Unknown';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EpisodeHistoryRow extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
isMarkAsFailedModalOpen: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onMarkAsFailedPress = () => {
|
|
||||||
this.setState({ isMarkAsFailedModalOpen: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
onConfirmMarkAsFailed = () => {
|
|
||||||
this.props.onMarkAsFailedPress(this.props.id);
|
|
||||||
this.setState({ isMarkAsFailedModalOpen: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
onMarkAsFailedModalClose = () => {
|
|
||||||
this.setState({ isMarkAsFailedModalOpen: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
eventType,
|
|
||||||
sourceTitle,
|
|
||||||
languages,
|
|
||||||
quality,
|
|
||||||
qualityCutoffNotMet,
|
|
||||||
customFormats,
|
|
||||||
customFormatScore,
|
|
||||||
date,
|
|
||||||
data,
|
|
||||||
downloadId
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const {
|
|
||||||
isMarkAsFailedModalOpen
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableRow>
|
|
||||||
<HistoryEventTypeCell
|
|
||||||
eventType={eventType}
|
|
||||||
data={data}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TableRowCell>
|
|
||||||
{sourceTitle}
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell>
|
|
||||||
<EpisodeLanguages languages={languages} />
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell>
|
|
||||||
<EpisodeQuality
|
|
||||||
quality={quality}
|
|
||||||
isCutoffNotMet={qualityCutoffNotMet}
|
|
||||||
/>
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell>
|
|
||||||
<EpisodeFormats formats={customFormats} />
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell>
|
|
||||||
{formatCustomFormatScore(customFormatScore, customFormats.length)}
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<RelativeDateCell
|
|
||||||
date={date}
|
|
||||||
includeSeconds={true}
|
|
||||||
includeTime={true}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TableRowCell className={styles.actions}>
|
|
||||||
<Popover
|
|
||||||
anchor={
|
|
||||||
<Icon
|
|
||||||
name={icons.INFO}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
title={getTitle(eventType)}
|
|
||||||
body={
|
|
||||||
<HistoryDetails
|
|
||||||
eventType={eventType}
|
|
||||||
sourceTitle={sourceTitle}
|
|
||||||
data={data}
|
|
||||||
downloadId={downloadId}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
position={tooltipPositions.LEFT}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{
|
|
||||||
eventType === 'grabbed' &&
|
|
||||||
<IconButton
|
|
||||||
title={translate('MarkAsFailed')}
|
|
||||||
name={icons.REMOVE}
|
|
||||||
size={14}
|
|
||||||
onPress={this.onMarkAsFailedPress}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<ConfirmModal
|
|
||||||
isOpen={isMarkAsFailedModalOpen}
|
|
||||||
kind={kinds.DANGER}
|
|
||||||
title={translate('MarkAsFailed')}
|
|
||||||
message={translate('MarkAsFailedConfirmation', { sourceTitle })}
|
|
||||||
confirmLabel={translate('MarkAsFailed')}
|
|
||||||
onConfirm={this.onConfirmMarkAsFailed}
|
|
||||||
onCancel={this.onMarkAsFailedModalClose}
|
|
||||||
/>
|
|
||||||
</TableRow>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
EpisodeHistoryRow.propTypes = {
|
|
||||||
id: PropTypes.number.isRequired,
|
|
||||||
eventType: PropTypes.string.isRequired,
|
|
||||||
sourceTitle: PropTypes.string.isRequired,
|
|
||||||
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
quality: PropTypes.object.isRequired,
|
|
||||||
qualityCutoffNotMet: PropTypes.bool.isRequired,
|
|
||||||
customFormats: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
customFormatScore: PropTypes.number.isRequired,
|
|
||||||
date: PropTypes.string.isRequired,
|
|
||||||
data: PropTypes.object.isRequired,
|
|
||||||
downloadId: PropTypes.string,
|
|
||||||
onMarkAsFailedPress: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EpisodeHistoryRow;
|
|
||||||
151
frontend/src/Episode/History/EpisodeHistoryRow.tsx
Normal file
151
frontend/src/Episode/History/EpisodeHistoryRow.tsx
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import HistoryDetails from 'Activity/History/Details/HistoryDetails';
|
||||||
|
import HistoryEventTypeCell from 'Activity/History/HistoryEventTypeCell';
|
||||||
|
import Icon from 'Components/Icon';
|
||||||
|
import IconButton from 'Components/Link/IconButton';
|
||||||
|
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||||
|
import RelativeDateCell from 'Components/Table/Cells/RelativeDateCell';
|
||||||
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
|
import TableRow from 'Components/Table/TableRow';
|
||||||
|
import Popover from 'Components/Tooltip/Popover';
|
||||||
|
import EpisodeFormats from 'Episode/EpisodeFormats';
|
||||||
|
import EpisodeLanguages from 'Episode/EpisodeLanguages';
|
||||||
|
import EpisodeQuality from 'Episode/EpisodeQuality';
|
||||||
|
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
||||||
|
import Language from 'Language/Language';
|
||||||
|
import { QualityModel } from 'Quality/Quality';
|
||||||
|
import CustomFormat from 'typings/CustomFormat';
|
||||||
|
import { HistoryData, HistoryEventType } from 'typings/History';
|
||||||
|
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import styles from './EpisodeHistoryRow.css';
|
||||||
|
|
||||||
|
function getTitle(eventType: HistoryEventType) {
|
||||||
|
switch (eventType) {
|
||||||
|
case 'grabbed':
|
||||||
|
return 'Grabbed';
|
||||||
|
case 'seriesFolderImported':
|
||||||
|
return 'Series Folder Imported';
|
||||||
|
case 'downloadFolderImported':
|
||||||
|
return 'Download Folder Imported';
|
||||||
|
case 'downloadFailed':
|
||||||
|
return 'Download Failed';
|
||||||
|
case 'episodeFileDeleted':
|
||||||
|
return 'Episode File Deleted';
|
||||||
|
case 'episodeFileRenamed':
|
||||||
|
return 'Episode File Renamed';
|
||||||
|
default:
|
||||||
|
return 'Unknown';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EpisodeHistoryRowProps {
|
||||||
|
id: number;
|
||||||
|
eventType: HistoryEventType;
|
||||||
|
sourceTitle: string;
|
||||||
|
languages: Language[];
|
||||||
|
quality: QualityModel;
|
||||||
|
qualityCutoffNotMet: boolean;
|
||||||
|
customFormats: CustomFormat[];
|
||||||
|
customFormatScore: number;
|
||||||
|
date: string;
|
||||||
|
data: HistoryData;
|
||||||
|
downloadId?: string;
|
||||||
|
onMarkAsFailedPress: (id: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function EpisodeHistoryRow({
|
||||||
|
id,
|
||||||
|
eventType,
|
||||||
|
sourceTitle,
|
||||||
|
languages,
|
||||||
|
quality,
|
||||||
|
qualityCutoffNotMet,
|
||||||
|
customFormats,
|
||||||
|
customFormatScore,
|
||||||
|
date,
|
||||||
|
data,
|
||||||
|
downloadId,
|
||||||
|
onMarkAsFailedPress,
|
||||||
|
}: EpisodeHistoryRowProps) {
|
||||||
|
const [isMarkAsFailedModalOpen, setIsMarkAsFailedModalOpen] = useState(false);
|
||||||
|
|
||||||
|
const handleMarkAsFailedPress = useCallback(() => {
|
||||||
|
setIsMarkAsFailedModalOpen(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleConfirmMarkAsFailed = useCallback(() => {
|
||||||
|
onMarkAsFailedPress(id);
|
||||||
|
setIsMarkAsFailedModalOpen(false);
|
||||||
|
}, [id, onMarkAsFailedPress]);
|
||||||
|
|
||||||
|
const handleMarkAsFailedModalClose = useCallback(() => {
|
||||||
|
setIsMarkAsFailedModalOpen(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow>
|
||||||
|
<HistoryEventTypeCell eventType={eventType} data={data} />
|
||||||
|
|
||||||
|
<TableRowCell>{sourceTitle}</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell>
|
||||||
|
<EpisodeLanguages languages={languages} />
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell>
|
||||||
|
<EpisodeQuality
|
||||||
|
quality={quality}
|
||||||
|
isCutoffNotMet={qualityCutoffNotMet}
|
||||||
|
/>
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell>
|
||||||
|
<EpisodeFormats formats={customFormats} />
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell>
|
||||||
|
{formatCustomFormatScore(customFormatScore, customFormats.length)}
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<RelativeDateCell date={date} includeSeconds={true} includeTime={true} />
|
||||||
|
|
||||||
|
<TableRowCell className={styles.actions}>
|
||||||
|
<Popover
|
||||||
|
anchor={<Icon name={icons.INFO} />}
|
||||||
|
title={getTitle(eventType)}
|
||||||
|
body={
|
||||||
|
<HistoryDetails
|
||||||
|
eventType={eventType}
|
||||||
|
sourceTitle={sourceTitle}
|
||||||
|
data={data}
|
||||||
|
downloadId={downloadId}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
position={tooltipPositions.LEFT}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{eventType === 'grabbed' && (
|
||||||
|
<IconButton
|
||||||
|
title={translate('MarkAsFailed')}
|
||||||
|
name={icons.REMOVE}
|
||||||
|
size={14}
|
||||||
|
onPress={handleMarkAsFailedPress}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<ConfirmModal
|
||||||
|
isOpen={isMarkAsFailedModalOpen}
|
||||||
|
kind={kinds.DANGER}
|
||||||
|
title={translate('MarkAsFailed')}
|
||||||
|
message={translate('MarkAsFailedConfirmation', { sourceTitle })}
|
||||||
|
confirmLabel={translate('MarkAsFailed')}
|
||||||
|
onConfirm={handleConfirmMarkAsFailed}
|
||||||
|
onCancel={handleMarkAsFailedModalClose}
|
||||||
|
/>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EpisodeHistoryRow;
|
||||||
|
|
@ -74,9 +74,6 @@ interface SelectEpisodeModalContentProps {
|
||||||
onModalClose(): unknown;
|
onModalClose(): unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
function SelectEpisodeModalContent(props: SelectEpisodeModalContentProps) {
|
function SelectEpisodeModalContent(props: SelectEpisodeModalContentProps) {
|
||||||
const {
|
const {
|
||||||
selectedIds,
|
selectedIds,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue