mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2025-09-17 17:14:18 +02:00
More Cleanup
This commit is contained in:
5
frontend/src/History/Details/HistoryDetails.css
Normal file
5
frontend/src/History/Details/HistoryDetails.css
Normal file
@@ -0,0 +1,5 @@
|
||||
.description {
|
||||
composes: description from '~Components/DescriptionList/DescriptionListItemDescription.css';
|
||||
|
||||
overflow-wrap: break-word;
|
||||
}
|
279
frontend/src/History/Details/HistoryDetails.js
Normal file
279
frontend/src/History/Details/HistoryDetails.js
Normal file
@@ -0,0 +1,279 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import DescriptionList from 'Components/DescriptionList/DescriptionList';
|
||||
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
|
||||
import DescriptionListItemDescription from 'Components/DescriptionList/DescriptionListItemDescription';
|
||||
import DescriptionListItemTitle from 'Components/DescriptionList/DescriptionListItemTitle';
|
||||
import Link from 'Components/Link/Link';
|
||||
import formatDateTime from 'Utilities/Date/formatDateTime';
|
||||
import formatAge from 'Utilities/Number/formatAge';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './HistoryDetails.css';
|
||||
|
||||
function HistoryDetails(props) {
|
||||
const {
|
||||
eventType,
|
||||
sourceTitle,
|
||||
data,
|
||||
shortDateFormat,
|
||||
timeFormat
|
||||
} = props;
|
||||
|
||||
if (eventType === 'grabbed') {
|
||||
const {
|
||||
indexer,
|
||||
releaseGroup,
|
||||
nzbInfoUrl,
|
||||
downloadClient,
|
||||
downloadId,
|
||||
age,
|
||||
ageHours,
|
||||
ageMinutes,
|
||||
publishedDate
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Name')}
|
||||
data={sourceTitle}
|
||||
/>
|
||||
|
||||
{
|
||||
!!indexer &&
|
||||
<DescriptionListItem
|
||||
title={translate('Indexer')}
|
||||
data={indexer}
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
!!releaseGroup &&
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('ReleaseGroup')}
|
||||
data={releaseGroup}
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
!!nzbInfoUrl &&
|
||||
<span>
|
||||
<DescriptionListItemTitle>
|
||||
Info URL
|
||||
</DescriptionListItemTitle>
|
||||
|
||||
<DescriptionListItemDescription>
|
||||
<Link to={nzbInfoUrl}>{nzbInfoUrl}</Link>
|
||||
</DescriptionListItemDescription>
|
||||
</span>
|
||||
}
|
||||
|
||||
{
|
||||
!!downloadClient &&
|
||||
<DescriptionListItem
|
||||
title={translate('DownloadClient')}
|
||||
data={downloadClient}
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
!!downloadId &&
|
||||
<DescriptionListItem
|
||||
title={translate('GrabID')}
|
||||
data={downloadId}
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
!!indexer &&
|
||||
<DescriptionListItem
|
||||
title={translate('AgeWhenGrabbed')}
|
||||
data={formatAge(age, ageHours, ageMinutes)}
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
!!publishedDate &&
|
||||
<DescriptionListItem
|
||||
title={translate('PublishedDate')}
|
||||
data={formatDateTime(publishedDate, shortDateFormat, timeFormat, { includeSeconds: true })}
|
||||
/>
|
||||
}
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
if (eventType === 'downloadFailed') {
|
||||
const {
|
||||
message
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Name')}
|
||||
data={sourceTitle}
|
||||
/>
|
||||
|
||||
{
|
||||
!!message &&
|
||||
<DescriptionListItem
|
||||
title={translate('Message')}
|
||||
data={message}
|
||||
/>
|
||||
}
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
if (eventType === 'downloadFolderImported') {
|
||||
const {
|
||||
droppedPath,
|
||||
importedPath
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Name')}
|
||||
data={sourceTitle}
|
||||
/>
|
||||
|
||||
{
|
||||
!!droppedPath &&
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Source')}
|
||||
data={droppedPath}
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
!!importedPath &&
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('ImportedTo')}
|
||||
data={importedPath}
|
||||
/>
|
||||
}
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
if (eventType === 'movieFileDeleted') {
|
||||
const {
|
||||
reason
|
||||
} = data;
|
||||
|
||||
let reasonMessage = '';
|
||||
|
||||
switch (reason) {
|
||||
case 'Manual':
|
||||
reasonMessage = 'File was deleted by via UI';
|
||||
break;
|
||||
case 'MissingFromDisk':
|
||||
reasonMessage = 'Prowlarr was unable to find the file on disk so it was removed';
|
||||
break;
|
||||
case 'Upgrade':
|
||||
reasonMessage = 'File was deleted to import an upgrade';
|
||||
break;
|
||||
default:
|
||||
reasonMessage = '';
|
||||
}
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
title={translate('Name')}
|
||||
data={sourceTitle}
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('Reason')}
|
||||
data={reasonMessage}
|
||||
/>
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
if (eventType === 'movieFileRenamed') {
|
||||
const {
|
||||
sourcePath,
|
||||
sourceRelativePath,
|
||||
path,
|
||||
relativePath
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
title={translate('SourcePath')}
|
||||
data={sourcePath}
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('SourceRelativePath')}
|
||||
data={sourceRelativePath}
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('DestinationPath')}
|
||||
data={path}
|
||||
/>
|
||||
|
||||
<DescriptionListItem
|
||||
title={translate('DestinationRelativePath')}
|
||||
data={relativePath}
|
||||
/>
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
if (eventType === 'downloadIgnored') {
|
||||
const {
|
||||
message
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Name')}
|
||||
data={sourceTitle}
|
||||
/>
|
||||
|
||||
{
|
||||
!!message &&
|
||||
<DescriptionListItem
|
||||
title={translate('Message')}
|
||||
data={message}
|
||||
/>
|
||||
}
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title={translate('Name')}
|
||||
data={sourceTitle}
|
||||
/>
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
HistoryDetails.propTypes = {
|
||||
eventType: PropTypes.string.isRequired,
|
||||
sourceTitle: PropTypes.string.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default HistoryDetails;
|
18
frontend/src/History/Details/HistoryDetailsConnector.js
Normal file
18
frontend/src/History/Details/HistoryDetailsConnector.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
import HistoryDetails from './HistoryDetails';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createUISettingsSelector(),
|
||||
(uiSettings) => {
|
||||
return {
|
||||
shortDateFormat: uiSettings.shortDateFormat,
|
||||
timeFormat: uiSettings.timeFormat
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(createMapStateToProps)(HistoryDetails);
|
5
frontend/src/History/Details/HistoryDetailsModal.css
Normal file
5
frontend/src/History/Details/HistoryDetailsModal.css
Normal file
@@ -0,0 +1,5 @@
|
||||
.markAsFailedButton {
|
||||
composes: button from '~Components/Link/Button.css';
|
||||
|
||||
margin-right: auto;
|
||||
}
|
107
frontend/src/History/Details/HistoryDetailsModal.js
Normal file
107
frontend/src/History/Details/HistoryDetailsModal.js
Normal file
@@ -0,0 +1,107 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Button from 'Components/Link/Button';
|
||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import HistoryDetails from './HistoryDetails';
|
||||
import styles from './HistoryDetailsModal.css';
|
||||
|
||||
function getHeaderTitle(eventType) {
|
||||
switch (eventType) {
|
||||
case 'grabbed':
|
||||
return 'Grabbed';
|
||||
case 'downloadFailed':
|
||||
return 'Download Failed';
|
||||
case 'downloadFolderImported':
|
||||
return 'Movie Imported';
|
||||
case 'movieFileDeleted':
|
||||
return 'Movie File Deleted';
|
||||
case 'movieFileRenamed':
|
||||
return 'Movie File Renamed';
|
||||
case 'downloadIgnored':
|
||||
return 'Download Ignored';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
function HistoryDetailsModal(props) {
|
||||
const {
|
||||
isOpen,
|
||||
eventType,
|
||||
sourceTitle,
|
||||
data,
|
||||
isMarkingAsFailed,
|
||||
shortDateFormat,
|
||||
timeFormat,
|
||||
onMarkAsFailedPress,
|
||||
onModalClose
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{getHeaderTitle(eventType)}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<HistoryDetails
|
||||
eventType={eventType}
|
||||
sourceTitle={sourceTitle}
|
||||
data={data}
|
||||
shortDateFormat={shortDateFormat}
|
||||
timeFormat={timeFormat}
|
||||
/>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
{
|
||||
eventType === 'grabbed' &&
|
||||
<SpinnerButton
|
||||
className={styles.markAsFailedButton}
|
||||
kind={kinds.DANGER}
|
||||
isSpinning={isMarkingAsFailed}
|
||||
onPress={onMarkAsFailedPress}
|
||||
>
|
||||
Mark as Failed
|
||||
</SpinnerButton>
|
||||
}
|
||||
|
||||
<Button
|
||||
onPress={onModalClose}
|
||||
>
|
||||
{translate('Close')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
HistoryDetailsModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
eventType: PropTypes.string.isRequired,
|
||||
sourceTitle: PropTypes.string.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
isMarkingAsFailed: PropTypes.bool.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
onMarkAsFailedPress: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
HistoryDetailsModal.defaultProps = {
|
||||
isMarkingAsFailed: false
|
||||
};
|
||||
|
||||
export default HistoryDetailsModal;
|
153
frontend/src/History/History.js
Normal file
153
frontend/src/History/History.js
Normal file
@@ -0,0 +1,153 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import TablePager from 'Components/Table/TablePager';
|
||||
import { align, icons } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import HistoryRowConnector from './HistoryRowConnector';
|
||||
|
||||
class History extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
isMoviesFetching,
|
||||
isMoviesPopulated,
|
||||
moviesError,
|
||||
items,
|
||||
columns,
|
||||
selectedFilterKey,
|
||||
filters,
|
||||
totalRecords,
|
||||
onFilterSelect,
|
||||
onFirstPagePress,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
const isFetchingAny = isFetching || isMoviesFetching;
|
||||
const isAllPopulated = isPopulated && (isMoviesPopulated || !items.length);
|
||||
const hasError = error || moviesError;
|
||||
|
||||
return (
|
||||
<PageContent title={translate('History')}>
|
||||
<PageToolbar>
|
||||
<PageToolbarSection>
|
||||
<PageToolbarButton
|
||||
label={translate('Refresh')}
|
||||
iconName={icons.REFRESH}
|
||||
isSpinning={isFetching}
|
||||
onPress={onFirstPagePress}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
|
||||
<PageToolbarSection alignContent={align.RIGHT}>
|
||||
<TableOptionsModalWrapper
|
||||
{...otherProps}
|
||||
columns={columns}
|
||||
>
|
||||
<PageToolbarButton
|
||||
label={translate('Options')}
|
||||
iconName={icons.TABLE}
|
||||
/>
|
||||
</TableOptionsModalWrapper>
|
||||
|
||||
<FilterMenu
|
||||
alignMenu={align.RIGHT}
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
filters={filters}
|
||||
customFilters={[]}
|
||||
onFilterSelect={onFilterSelect}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
</PageToolbar>
|
||||
|
||||
<PageContentBody>
|
||||
{
|
||||
isFetchingAny && !isAllPopulated &&
|
||||
<LoadingIndicator />
|
||||
}
|
||||
|
||||
{
|
||||
!isFetchingAny && hasError &&
|
||||
<div>
|
||||
{translate('UnableToLoadHistory')}
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
// If history isPopulated and it's empty show no history found and don't
|
||||
// wait for the episodes to populate because they are never coming.
|
||||
|
||||
isPopulated && !hasError && !items.length &&
|
||||
<div>
|
||||
No history found
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
isAllPopulated && !hasError && !!items.length &&
|
||||
<div>
|
||||
<Table
|
||||
columns={columns}
|
||||
{...otherProps}
|
||||
>
|
||||
<TableBody>
|
||||
{
|
||||
items.map((item) => {
|
||||
return (
|
||||
<HistoryRowConnector
|
||||
key={item.id}
|
||||
columns={columns}
|
||||
{...item}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
<TablePager
|
||||
totalRecords={totalRecords}
|
||||
isFetching={isFetching}
|
||||
onFirstPagePress={onFirstPagePress}
|
||||
{...otherProps}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</PageContentBody>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
History.propTypes = {
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
isMoviesFetching: PropTypes.bool.isRequired,
|
||||
isMoviesPopulated: PropTypes.bool.isRequired,
|
||||
moviesError: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
selectedFilterKey: PropTypes.string.isRequired,
|
||||
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
totalRecords: PropTypes.number,
|
||||
onFilterSelect: PropTypes.func.isRequired,
|
||||
onFirstPagePress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default History;
|
138
frontend/src/History/HistoryConnector.js
Normal file
138
frontend/src/History/HistoryConnector.js
Normal file
@@ -0,0 +1,138 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import withCurrentPage from 'Components/withCurrentPage';
|
||||
import * as historyActions from 'Store/Actions/historyActions';
|
||||
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
||||
import History from './History';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.history,
|
||||
(state) => state.movies,
|
||||
(history, movies) => {
|
||||
return {
|
||||
isMoviesFetching: movies.isFetching,
|
||||
isMoviesPopulated: movies.isPopulated,
|
||||
moviesError: movies.error,
|
||||
...history
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
...historyActions
|
||||
};
|
||||
|
||||
class HistoryConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
useCurrentPage,
|
||||
fetchHistory,
|
||||
gotoHistoryFirstPage
|
||||
} = this.props;
|
||||
|
||||
registerPagePopulator(this.repopulate);
|
||||
|
||||
if (useCurrentPage) {
|
||||
fetchHistory();
|
||||
} else {
|
||||
gotoHistoryFirstPage();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
unregisterPagePopulator(this.repopulate);
|
||||
this.props.clearHistory();
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
repopulate = () => {
|
||||
this.props.fetchHistory();
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onFirstPagePress = () => {
|
||||
this.props.gotoHistoryFirstPage();
|
||||
}
|
||||
|
||||
onPreviousPagePress = () => {
|
||||
this.props.gotoHistoryPreviousPage();
|
||||
}
|
||||
|
||||
onNextPagePress = () => {
|
||||
this.props.gotoHistoryNextPage();
|
||||
}
|
||||
|
||||
onLastPagePress = () => {
|
||||
this.props.gotoHistoryLastPage();
|
||||
}
|
||||
|
||||
onPageSelect = (page) => {
|
||||
this.props.gotoHistoryPage({ page });
|
||||
}
|
||||
|
||||
onSortPress = (sortKey) => {
|
||||
this.props.setHistorySort({ sortKey });
|
||||
}
|
||||
|
||||
onFilterSelect = (selectedFilterKey) => {
|
||||
this.props.setHistoryFilter({ selectedFilterKey });
|
||||
}
|
||||
|
||||
onTableOptionChange = (payload) => {
|
||||
this.props.setHistoryTableOption(payload);
|
||||
|
||||
if (payload.pageSize) {
|
||||
this.props.gotoHistoryFirstPage();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<History
|
||||
onFirstPagePress={this.onFirstPagePress}
|
||||
onPreviousPagePress={this.onPreviousPagePress}
|
||||
onNextPagePress={this.onNextPagePress}
|
||||
onLastPagePress={this.onLastPagePress}
|
||||
onPageSelect={this.onPageSelect}
|
||||
onSortPress={this.onSortPress}
|
||||
onFilterSelect={this.onFilterSelect}
|
||||
onTableOptionChange={this.onTableOptionChange}
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
HistoryConnector.propTypes = {
|
||||
useCurrentPage: PropTypes.bool.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
fetchHistory: PropTypes.func.isRequired,
|
||||
gotoHistoryFirstPage: PropTypes.func.isRequired,
|
||||
gotoHistoryPreviousPage: PropTypes.func.isRequired,
|
||||
gotoHistoryNextPage: PropTypes.func.isRequired,
|
||||
gotoHistoryLastPage: PropTypes.func.isRequired,
|
||||
gotoHistoryPage: PropTypes.func.isRequired,
|
||||
setHistorySort: PropTypes.func.isRequired,
|
||||
setHistoryFilter: PropTypes.func.isRequired,
|
||||
setHistoryTableOption: PropTypes.func.isRequired,
|
||||
clearHistory: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default withCurrentPage(
|
||||
connect(createMapStateToProps, mapDispatchToProps)(HistoryConnector)
|
||||
);
|
6
frontend/src/History/HistoryEventTypeCell.css
Normal file
6
frontend/src/History/HistoryEventTypeCell.css
Normal file
@@ -0,0 +1,6 @@
|
||||
.cell {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 35px;
|
||||
text-align: center;
|
||||
}
|
86
frontend/src/History/HistoryEventTypeCell.js
Normal file
86
frontend/src/History/HistoryEventTypeCell.js
Normal file
@@ -0,0 +1,86 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import styles from './HistoryEventTypeCell.css';
|
||||
|
||||
function getIconName(eventType) {
|
||||
switch (eventType) {
|
||||
case 'grabbed':
|
||||
return icons.DOWNLOADING;
|
||||
case 'movieFolderImported':
|
||||
return icons.DRIVE;
|
||||
case 'downloadFolderImported':
|
||||
return icons.DOWNLOADED;
|
||||
case 'downloadFailed':
|
||||
return icons.DOWNLOADING;
|
||||
case 'movieFileDeleted':
|
||||
return icons.DELETE;
|
||||
case 'movieFileRenamed':
|
||||
return icons.ORGANIZE;
|
||||
case 'downloadIgnored':
|
||||
return icons.IGNORE;
|
||||
default:
|
||||
return icons.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
function getIconKind(eventType) {
|
||||
switch (eventType) {
|
||||
case 'downloadFailed':
|
||||
return kinds.DANGER;
|
||||
default:
|
||||
return kinds.DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
function getTooltip(eventType, data) {
|
||||
switch (eventType) {
|
||||
case 'grabbed':
|
||||
return `Movie grabbed from ${data.indexer} and sent to ${data.downloadClient}`;
|
||||
case 'movieFolderImported':
|
||||
return 'Movie imported from movie folder';
|
||||
case 'downloadFolderImported':
|
||||
return 'Movie downloaded successfully and picked up from download client';
|
||||
case 'downloadFailed':
|
||||
return 'Movie download failed';
|
||||
case 'movieFileDeleted':
|
||||
return 'Movie file deleted';
|
||||
case 'movieFileRenamed':
|
||||
return 'Movie file renamed';
|
||||
case 'downloadIgnored':
|
||||
return 'Movie Download Ignored';
|
||||
default:
|
||||
return 'Unknown event';
|
||||
}
|
||||
}
|
||||
|
||||
function HistoryEventTypeCell({ eventType, data }) {
|
||||
const iconName = getIconName(eventType);
|
||||
const iconKind = getIconKind(eventType);
|
||||
const tooltip = getTooltip(eventType, data);
|
||||
|
||||
return (
|
||||
<TableRowCell
|
||||
className={styles.cell}
|
||||
title={tooltip}
|
||||
>
|
||||
<Icon
|
||||
name={iconName}
|
||||
kind={iconKind}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
HistoryEventTypeCell.propTypes = {
|
||||
eventType: PropTypes.string.isRequired,
|
||||
data: PropTypes.object
|
||||
};
|
||||
|
||||
HistoryEventTypeCell.defaultProps = {
|
||||
data: {}
|
||||
};
|
||||
|
||||
export default HistoryEventTypeCell;
|
23
frontend/src/History/HistoryRow.css
Normal file
23
frontend/src/History/HistoryRow.css
Normal file
@@ -0,0 +1,23 @@
|
||||
.downloadClient {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.indexer {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.releaseGroup {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 110px;
|
||||
}
|
||||
|
||||
.details {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 30px;
|
||||
}
|
236
frontend/src/History/HistoryRow.js
Normal file
236
frontend/src/History/HistoryRow.js
Normal file
@@ -0,0 +1,236 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import MovieFormats from 'Indexer/MovieFormats';
|
||||
import MovieLanguage from 'Indexer/MovieLanguage';
|
||||
import MovieQuality from 'Indexer/MovieQuality';
|
||||
import MovieTitleLink from 'Indexer/MovieTitleLink';
|
||||
import HistoryDetailsModal from './Details/HistoryDetailsModal';
|
||||
import HistoryEventTypeCell from './HistoryEventTypeCell';
|
||||
import styles from './HistoryRow.css';
|
||||
|
||||
class HistoryRow extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isDetailsModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (
|
||||
prevProps.isMarkingAsFailed &&
|
||||
!this.props.isMarkingAsFailed &&
|
||||
!this.props.markAsFailedError
|
||||
) {
|
||||
this.setState({ isDetailsModalOpen: false });
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onDetailsPress = () => {
|
||||
this.setState({ isDetailsModalOpen: true });
|
||||
}
|
||||
|
||||
onDetailsModalClose = () => {
|
||||
this.setState({ isDetailsModalOpen: false });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
movie,
|
||||
quality,
|
||||
customFormats,
|
||||
languages,
|
||||
qualityCutoffNotMet,
|
||||
eventType,
|
||||
sourceTitle,
|
||||
date,
|
||||
data,
|
||||
isMarkingAsFailed,
|
||||
columns,
|
||||
shortDateFormat,
|
||||
timeFormat,
|
||||
onMarkAsFailedPress
|
||||
} = this.props;
|
||||
|
||||
if (!movie) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
{
|
||||
columns.map((column) => {
|
||||
const {
|
||||
name,
|
||||
isVisible
|
||||
} = column;
|
||||
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (name === 'eventType') {
|
||||
return (
|
||||
<HistoryEventTypeCell
|
||||
key={name}
|
||||
eventType={eventType}
|
||||
data={data}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'movies.sortTitle') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
<MovieTitleLink
|
||||
titleSlug={movie.titleSlug}
|
||||
title={movie.title}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'languages') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
<MovieLanguage
|
||||
languages={languages}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'quality') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
<MovieQuality
|
||||
quality={quality}
|
||||
isCutoffMet={qualityCutoffNotMet}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'customFormats') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
<MovieFormats
|
||||
formats={customFormats}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'date') {
|
||||
return (
|
||||
<RelativeDateCellConnector
|
||||
key={name}
|
||||
date={date}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'downloadClient') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.downloadClient}
|
||||
>
|
||||
{data.downloadClient}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'indexer') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.indexer}
|
||||
>
|
||||
{data.indexer}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'releaseGroup') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.releaseGroup}
|
||||
>
|
||||
{data.releaseGroup}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'details') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.details}
|
||||
>
|
||||
<IconButton
|
||||
name={icons.INFO}
|
||||
onPress={this.onDetailsPress}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
}
|
||||
|
||||
<HistoryDetailsModal
|
||||
isOpen={this.state.isDetailsModalOpen}
|
||||
eventType={eventType}
|
||||
sourceTitle={sourceTitle}
|
||||
data={data}
|
||||
isMarkingAsFailed={isMarkingAsFailed}
|
||||
shortDateFormat={shortDateFormat}
|
||||
timeFormat={timeFormat}
|
||||
onMarkAsFailedPress={onMarkAsFailedPress}
|
||||
onModalClose={this.onDetailsModalClose}
|
||||
/>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
HistoryRow.propTypes = {
|
||||
movieId: PropTypes.number,
|
||||
movie: PropTypes.object.isRequired,
|
||||
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
quality: PropTypes.object.isRequired,
|
||||
qualityCutoffNotMet: PropTypes.bool.isRequired,
|
||||
customFormats: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
eventType: PropTypes.string.isRequired,
|
||||
sourceTitle: PropTypes.string.isRequired,
|
||||
date: PropTypes.string.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
isMarkingAsFailed: PropTypes.bool,
|
||||
markAsFailedError: PropTypes.object,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
onMarkAsFailedPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default HistoryRow;
|
73
frontend/src/History/HistoryRowConnector.js
Normal file
73
frontend/src/History/HistoryRowConnector.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { fetchHistory, markAsFailed } from 'Store/Actions/historyActions';
|
||||
import createMovieSelector from 'Store/Selectors/createMovieSelector';
|
||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
import HistoryRow from './HistoryRow';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createMovieSelector(),
|
||||
createUISettingsSelector(),
|
||||
(movie, uiSettings) => {
|
||||
return {
|
||||
movie,
|
||||
shortDateFormat: uiSettings.shortDateFormat,
|
||||
timeFormat: uiSettings.timeFormat
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchHistory,
|
||||
markAsFailed
|
||||
};
|
||||
|
||||
class HistoryRowConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (
|
||||
prevProps.isMarkingAsFailed &&
|
||||
!this.props.isMarkingAsFailed &&
|
||||
!this.props.markAsFailedError
|
||||
) {
|
||||
this.props.fetchHistory();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onMarkAsFailedPress = () => {
|
||||
this.props.markAsFailed({ id: this.props.id });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<HistoryRow
|
||||
{...this.props}
|
||||
onMarkAsFailedPress={this.onMarkAsFailedPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
HistoryRowConnector.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
isMarkingAsFailed: PropTypes.bool,
|
||||
markAsFailedError: PropTypes.object,
|
||||
fetchHistory: PropTypes.func.isRequired,
|
||||
markAsFailed: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(HistoryRowConnector);
|
Reference in New Issue
Block a user