mirror of
https://github.com/Radarr/Radarr.git
synced 2026-04-18 21:35:51 -04:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 200be6451a | |||
| b279984bd7 | |||
| 3f6f4fc65f |
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[](https://dev.azure.com/Radarr/Radarr/_build/latest?definitionId=1&branchName=develop)
|
[](https://dev.azure.com/Radarr/Radarr/_build/latest?definitionId=1&branchName=develop)
|
||||||
[](https://translate.servarr.com/engage/radarr/?utm_source=widget)
|
[](https://translate.servarr.com/engage/radarr/?utm_source=widget)
|
||||||
[](https://wiki.servarr.com/radarr/installation/docker)
|
[](https://wiki.servarr.com/radarr/installation#docker)
|
||||||

|

|
||||||
[](#backers)
|
[](#backers)
|
||||||
[](#sponsors)
|
[](#sponsors)
|
||||||
|
|||||||
+3
-4
@@ -9,15 +9,15 @@ variables:
|
|||||||
testsFolder: './_tests'
|
testsFolder: './_tests'
|
||||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||||
majorVersion: '5.3.2'
|
majorVersion: '5.1.2'
|
||||||
minorVersion: $[counter('minorVersion', 2000)]
|
minorVersion: $[counter('minorVersion', 2000)]
|
||||||
radarrVersion: '$(majorVersion).$(minorVersion)'
|
radarrVersion: '$(majorVersion).$(minorVersion)'
|
||||||
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
||||||
sentryOrg: 'servarr'
|
sentryOrg: 'servarr'
|
||||||
sentryUrl: 'https://sentry.servarr.com'
|
sentryUrl: 'https://sentry.servarr.com'
|
||||||
dotnetVersion: '6.0.417'
|
dotnetVersion: '6.0.413'
|
||||||
nodeVersion: '16.X'
|
nodeVersion: '16.X'
|
||||||
innoVersion: '6.2.2'
|
innoVersion: '6.2.0'
|
||||||
windowsImage: 'windows-2022'
|
windowsImage: 'windows-2022'
|
||||||
linuxImage: 'ubuntu-20.04'
|
linuxImage: 'ubuntu-20.04'
|
||||||
macImage: 'macOS-11'
|
macImage: 'macOS-11'
|
||||||
@@ -1242,7 +1242,6 @@ stages:
|
|||||||
- stage: Report_Out
|
- stage: Report_Out
|
||||||
dependsOn:
|
dependsOn:
|
||||||
- Analyze
|
- Analyze
|
||||||
- Installer
|
|
||||||
- Unit_Test
|
- Unit_Test
|
||||||
- Integration
|
- Integration
|
||||||
- Automation
|
- Automation
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ InstallInno()
|
|||||||
ProgressStart "Installing portable Inno Setup"
|
ProgressStart "Installing portable Inno Setup"
|
||||||
|
|
||||||
rm -rf _inno
|
rm -rf _inno
|
||||||
curl -s --output innosetup.exe "https://files.jrsoftware.org/is/6/innosetup-${INNOVERSION:-6.2.2}.exe"
|
curl -s --output innosetup.exe "https://files.jrsoftware.org/is/6/innosetup-${INNOVERSION:-6.2.0}.exe"
|
||||||
mkdir _inno
|
mkdir _inno
|
||||||
./innosetup.exe //portable=1 //silent //currentuser //dir=.\\_inno
|
./innosetup.exe //portable=1 //silent //currentuser //dir=.\\_inno
|
||||||
rm innosetup.exe
|
rm innosetup.exe
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ const loose = true;
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: [
|
plugins: [
|
||||||
'@babel/plugin-transform-logical-assignment-operators',
|
|
||||||
|
|
||||||
// Stage 1
|
// Stage 1
|
||||||
'@babel/plugin-proposal-export-default-from',
|
'@babel/plugin-proposal-export-default-from',
|
||||||
['@babel/plugin-transform-optional-chaining', { loose }],
|
['@babel/plugin-transform-optional-chaining', { loose }],
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ class Blocklist extends Component {
|
|||||||
lastToggled: null,
|
lastToggled: null,
|
||||||
selectedState: {},
|
selectedState: {},
|
||||||
isConfirmRemoveModalOpen: false,
|
isConfirmRemoveModalOpen: false,
|
||||||
isConfirmClearModalOpen: false,
|
|
||||||
items: props.items
|
items: props.items
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -91,19 +90,6 @@ class Blocklist extends Component {
|
|||||||
this.setState({ isConfirmRemoveModalOpen: false });
|
this.setState({ isConfirmRemoveModalOpen: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
onClearBlocklistPress = () => {
|
|
||||||
this.setState({ isConfirmClearModalOpen: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
onClearBlocklistConfirmed = () => {
|
|
||||||
this.props.onClearBlocklistPress();
|
|
||||||
this.setState({ isConfirmClearModalOpen: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
onConfirmClearModalClose = () => {
|
|
||||||
this.setState({ isConfirmClearModalOpen: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
@@ -117,6 +103,7 @@ class Blocklist extends Component {
|
|||||||
totalRecords,
|
totalRecords,
|
||||||
isRemoving,
|
isRemoving,
|
||||||
isClearingBlocklistExecuting,
|
isClearingBlocklistExecuting,
|
||||||
|
onClearBlocklistPress,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@@ -124,8 +111,7 @@ class Blocklist extends Component {
|
|||||||
allSelected,
|
allSelected,
|
||||||
allUnselected,
|
allUnselected,
|
||||||
selectedState,
|
selectedState,
|
||||||
isConfirmRemoveModalOpen,
|
isConfirmRemoveModalOpen
|
||||||
isConfirmClearModalOpen
|
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const selectedIds = this.getSelectedIds();
|
const selectedIds = this.getSelectedIds();
|
||||||
@@ -145,9 +131,8 @@ class Blocklist extends Component {
|
|||||||
<PageToolbarButton
|
<PageToolbarButton
|
||||||
label={translate('Clear')}
|
label={translate('Clear')}
|
||||||
iconName={icons.CLEAR}
|
iconName={icons.CLEAR}
|
||||||
isDisabled={!items.length}
|
|
||||||
isSpinning={isClearingBlocklistExecuting}
|
isSpinning={isClearingBlocklistExecuting}
|
||||||
onPress={this.onClearBlocklistPress}
|
onPress={onClearBlocklistPress}
|
||||||
/>
|
/>
|
||||||
</PageToolbarSection>
|
</PageToolbarSection>
|
||||||
|
|
||||||
@@ -230,16 +215,6 @@ class Blocklist extends Component {
|
|||||||
onConfirm={this.onRemoveSelectedConfirmed}
|
onConfirm={this.onRemoveSelectedConfirmed}
|
||||||
onCancel={this.onConfirmRemoveModalClose}
|
onCancel={this.onConfirmRemoveModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ConfirmModal
|
|
||||||
isOpen={isConfirmClearModalOpen}
|
|
||||||
kind={kinds.DANGER}
|
|
||||||
title={translate('ClearBlocklist')}
|
|
||||||
message={translate('ClearBlocklistMessageText')}
|
|
||||||
confirmLabel={translate('Clear')}
|
|
||||||
onConfirm={this.onClearBlocklistConfirmed}
|
|
||||||
onCancel={this.onConfirmClearModalClose}
|
|
||||||
/>
|
|
||||||
</PageContent>
|
</PageContent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { icons, kinds } from 'Helpers/Props';
|
|||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import styles from './HistoryEventTypeCell.css';
|
import styles from './HistoryEventTypeCell.css';
|
||||||
|
|
||||||
function getIconName(eventType, data) {
|
function getIconName(eventType) {
|
||||||
switch (eventType) {
|
switch (eventType) {
|
||||||
case 'grabbed':
|
case 'grabbed':
|
||||||
return icons.DOWNLOADING;
|
return icons.DOWNLOADING;
|
||||||
@@ -17,7 +17,7 @@ function getIconName(eventType, data) {
|
|||||||
case 'downloadFailed':
|
case 'downloadFailed':
|
||||||
return icons.DOWNLOADING;
|
return icons.DOWNLOADING;
|
||||||
case 'movieFileDeleted':
|
case 'movieFileDeleted':
|
||||||
return data.reason === 'MissingFromDisk' ? icons.FILE_MISSING : icons.DELETE;
|
return icons.DELETE;
|
||||||
case 'movieFileRenamed':
|
case 'movieFileRenamed':
|
||||||
return icons.ORGANIZE;
|
return icons.ORGANIZE;
|
||||||
case 'downloadIgnored':
|
case 'downloadIgnored':
|
||||||
@@ -47,7 +47,7 @@ function getTooltip(eventType, data) {
|
|||||||
case 'downloadFailed':
|
case 'downloadFailed':
|
||||||
return translate('MovieDownloadFailedTooltip');
|
return translate('MovieDownloadFailedTooltip');
|
||||||
case 'movieFileDeleted':
|
case 'movieFileDeleted':
|
||||||
return data.reason === 'MissingFromDisk' ? translate('MovieFileMissingTooltip') : translate('MovieFileDeletedTooltip');
|
return translate('MovieFileDeletedTooltip');
|
||||||
case 'movieFileRenamed':
|
case 'movieFileRenamed':
|
||||||
return translate('MovieFileRenamedTooltip');
|
return translate('MovieFileRenamedTooltip');
|
||||||
case 'downloadIgnored':
|
case 'downloadIgnored':
|
||||||
@@ -58,7 +58,7 @@ function getTooltip(eventType, data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function HistoryEventTypeCell({ eventType, data }) {
|
function HistoryEventTypeCell({ eventType, data }) {
|
||||||
const iconName = getIconName(eventType, data);
|
const iconName = getIconName(eventType);
|
||||||
const iconKind = getIconKind(eventType);
|
const iconKind = getIconKind(eventType);
|
||||||
const tooltip = getTooltip(eventType, data);
|
const tooltip = getTooltip(eventType, data);
|
||||||
|
|
||||||
|
|||||||
@@ -81,9 +81,4 @@ QueueDetails.propTypes = {
|
|||||||
progressBar: PropTypes.node.isRequired
|
progressBar: PropTypes.node.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
QueueDetails.defaultProps = {
|
|
||||||
trackedDownloadStatus: 'ok',
|
|
||||||
trackedDownloadState: 'downloading'
|
|
||||||
};
|
|
||||||
|
|
||||||
export default QueueDetails;
|
export default QueueDetails;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
|
|||||||
import IconButton from 'Components/Link/IconButton';
|
import IconButton from 'Components/Link/IconButton';
|
||||||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||||
import ProgressBar from 'Components/ProgressBar';
|
import ProgressBar from 'Components/ProgressBar';
|
||||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
// import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
||||||
import TableRow from 'Components/Table/TableRow';
|
import TableRow from 'Components/Table/TableRow';
|
||||||
@@ -97,7 +97,6 @@ class QueueRow extends Component {
|
|||||||
outputPath,
|
outputPath,
|
||||||
downloadClient,
|
downloadClient,
|
||||||
estimatedCompletionTime,
|
estimatedCompletionTime,
|
||||||
added,
|
|
||||||
timeleft,
|
timeleft,
|
||||||
size,
|
size,
|
||||||
sizeleft,
|
sizeleft,
|
||||||
@@ -316,15 +315,6 @@ class QueueRow extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name === 'added') {
|
|
||||||
return (
|
|
||||||
<RelativeDateCellConnector
|
|
||||||
key={name}
|
|
||||||
date={added}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'actions') {
|
if (name === 'actions') {
|
||||||
return (
|
return (
|
||||||
<TableRowCell
|
<TableRowCell
|
||||||
@@ -403,7 +393,6 @@ QueueRow.propTypes = {
|
|||||||
outputPath: PropTypes.string,
|
outputPath: PropTypes.string,
|
||||||
downloadClient: PropTypes.string,
|
downloadClient: PropTypes.string,
|
||||||
estimatedCompletionTime: PropTypes.string,
|
estimatedCompletionTime: PropTypes.string,
|
||||||
added: PropTypes.string,
|
|
||||||
timeleft: PropTypes.string,
|
timeleft: PropTypes.string,
|
||||||
size: PropTypes.number,
|
size: PropTypes.number,
|
||||||
year: PropTypes.number,
|
year: PropTypes.number,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import { tooltipPositions } from 'Helpers/Props';
|
import { tooltipPositions } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
import QueueStatus from './QueueStatus';
|
import QueueStatus from './QueueStatus';
|
||||||
import styles from './QueueStatusCell.css';
|
import styles from './QueueStatusCell.css';
|
||||||
|
|
||||||
@@ -40,8 +41,8 @@ QueueStatusCell.propTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
QueueStatusCell.defaultProps = {
|
QueueStatusCell.defaultProps = {
|
||||||
trackedDownloadStatus: 'ok',
|
trackedDownloadStatus: translate('Ok'),
|
||||||
trackedDownloadState: 'downloading'
|
trackedDownloadState: translate('Downloading')
|
||||||
};
|
};
|
||||||
|
|
||||||
export default QueueStatusCell;
|
export default QueueStatusCell;
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Icon from 'Components/Icon';
|
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
|
||||||
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
|
||||||
import formatTime from 'Utilities/Date/formatTime';
|
import formatTime from 'Utilities/Date/formatTime';
|
||||||
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
|
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
|
||||||
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
||||||
@@ -28,13 +25,11 @@ function TimeleftCell(props) {
|
|||||||
const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true });
|
const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRowCell className={styles.timeleft}>
|
<TableRowCell
|
||||||
<Tooltip
|
className={styles.timeleft}
|
||||||
anchor={<Icon name={icons.INFO} />}
|
title={translate('DelayingDownloadUntil', { date, time })}
|
||||||
tooltip={translate('DelayingDownloadUntil', { date, time })}
|
>
|
||||||
kind={kinds.INVERSE}
|
-
|
||||||
position={tooltipPositions.TOP}
|
|
||||||
/>
|
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -44,13 +39,11 @@ function TimeleftCell(props) {
|
|||||||
const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true });
|
const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRowCell className={styles.timeleft}>
|
<TableRowCell
|
||||||
<Tooltip
|
className={styles.timeleft}
|
||||||
anchor={<Icon name={icons.INFO} />}
|
title={translate('RetryingDownloadOn', { date, time })}
|
||||||
tooltip={translate('RetryingDownloadOn', { date, time })}
|
>
|
||||||
kind={kinds.INVERSE}
|
-
|
||||||
position={tooltipPositions.TOP}
|
|
||||||
/>
|
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import Alert from 'Components/Alert';
|
|
||||||
import TextInput from 'Components/Form/TextInput';
|
import TextInput from 'Components/Form/TextInput';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import Button from 'Components/Link/Button';
|
import Button from 'Components/Link/Button';
|
||||||
@@ -131,12 +130,7 @@ class AddNewMovie extends Component {
|
|||||||
<div className={styles.helpText}>
|
<div className={styles.helpText}>
|
||||||
{translate('FailedLoadingSearchResults')}
|
{translate('FailedLoadingSearchResults')}
|
||||||
</div>
|
</div>
|
||||||
<Alert kind={kinds.WARNING}>{getErrorMessage(error)}</Alert>
|
<div>{getErrorMessage(error)}</div>
|
||||||
<div>
|
|
||||||
<Link to="https://wiki.servarr.com/radarr/troubleshooting#invalid-response-received-from-tmdb">
|
|
||||||
{translate('WhySearchesCouldBeFailing')}
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div> : null
|
</div> : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,13 +85,8 @@
|
|||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.studio,
|
|
||||||
.genres {
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.links {
|
.links {
|
||||||
margin-left: 5px;
|
margin-left: 8px;
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ interface CssExports {
|
|||||||
'certification': string;
|
'certification': string;
|
||||||
'content': string;
|
'content': string;
|
||||||
'exclusionIcon': string;
|
'exclusionIcon': string;
|
||||||
'genres': string;
|
|
||||||
'icons': string;
|
'icons': string;
|
||||||
'links': string;
|
'links': string;
|
||||||
'overlay': string;
|
'overlay': string;
|
||||||
@@ -15,7 +14,6 @@ interface CssExports {
|
|||||||
'runtime': string;
|
'runtime': string;
|
||||||
'searchResult': string;
|
'searchResult': string;
|
||||||
'statusContainer': string;
|
'statusContainer': string;
|
||||||
'studio': string;
|
|
||||||
'title': string;
|
'title': string;
|
||||||
'titleContainer': string;
|
'titleContainer': string;
|
||||||
'titleRow': string;
|
'titleRow': string;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import ImdbRating from 'Components/ImdbRating';
|
|
||||||
import Label from 'Components/Label';
|
import Label from 'Components/Label';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
import TmdbRating from 'Components/TmdbRating';
|
import TmdbRating from 'Components/TmdbRating';
|
||||||
@@ -62,13 +61,11 @@ class AddNewMovieSearchResult extends Component {
|
|||||||
titleSlug,
|
titleSlug,
|
||||||
year,
|
year,
|
||||||
studio,
|
studio,
|
||||||
genres,
|
|
||||||
status,
|
status,
|
||||||
overview,
|
overview,
|
||||||
ratings,
|
ratings,
|
||||||
folder,
|
folder,
|
||||||
images,
|
images,
|
||||||
existingMovieId,
|
|
||||||
isExistingMovie,
|
isExistingMovie,
|
||||||
isExclusionMovie,
|
isExclusionMovie,
|
||||||
isSmallScreen,
|
isSmallScreen,
|
||||||
@@ -77,8 +74,8 @@ class AddNewMovieSearchResult extends Component {
|
|||||||
monitored,
|
monitored,
|
||||||
hasFile,
|
hasFile,
|
||||||
isAvailable,
|
isAvailable,
|
||||||
movieFile,
|
queueStatus,
|
||||||
queueItem,
|
queueState,
|
||||||
runtime,
|
runtime,
|
||||||
movieRuntimeFormat,
|
movieRuntimeFormat,
|
||||||
certification
|
certification
|
||||||
@@ -123,13 +120,13 @@ class AddNewMovieSearchResult extends Component {
|
|||||||
{
|
{
|
||||||
isExistingMovie &&
|
isExistingMovie &&
|
||||||
<MovieIndexProgressBar
|
<MovieIndexProgressBar
|
||||||
movieId={existingMovieId}
|
|
||||||
movieFile={movieFile}
|
|
||||||
monitored={monitored}
|
monitored={monitored}
|
||||||
hasFile={hasFile}
|
hasFile={hasFile}
|
||||||
status={status}
|
status={status}
|
||||||
width={posterWidth}
|
width={posterWidth}
|
||||||
detailedProgressBar={true}
|
detailedProgressBar={true}
|
||||||
|
queueStatus={queueStatus}
|
||||||
|
queueState={queueState}
|
||||||
isAvailable={isAvailable}
|
isAvailable={isAvailable}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@@ -200,46 +197,13 @@ class AddNewMovieSearchResult extends Component {
|
|||||||
/>
|
/>
|
||||||
</Label>
|
</Label>
|
||||||
|
|
||||||
{
|
|
||||||
ratings.imdb ?
|
|
||||||
<Label size={sizes.LARGE}>
|
|
||||||
<ImdbRating
|
|
||||||
ratings={ratings}
|
|
||||||
iconSize={13}
|
|
||||||
/>
|
|
||||||
</Label> :
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
!!studio &&
|
!!studio &&
|
||||||
<Label size={sizes.LARGE}>
|
<Label size={sizes.LARGE}>
|
||||||
<Icon
|
{studio}
|
||||||
name={icons.STUDIO}
|
|
||||||
size={13}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<span className={styles.studio}>
|
|
||||||
{studio}
|
|
||||||
</span>
|
|
||||||
</Label>
|
</Label>
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
genres.length > 0 ?
|
|
||||||
<Label size={sizes.LARGE}>
|
|
||||||
<Icon
|
|
||||||
name={icons.GENRE}
|
|
||||||
size={13}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<span className={styles.genres}>
|
|
||||||
{genres.slice(0, 3).join(', ')}
|
|
||||||
</span>
|
|
||||||
</Label> :
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
anchor={
|
anchor={
|
||||||
<Label
|
<Label
|
||||||
@@ -251,15 +215,15 @@ class AddNewMovieSearchResult extends Component {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<span className={styles.links}>
|
<span className={styles.links}>
|
||||||
{translate('Links')}
|
Links
|
||||||
</span>
|
</span>
|
||||||
</Label>
|
</Label>
|
||||||
}
|
}
|
||||||
tooltip={
|
tooltip={
|
||||||
<MovieDetailsLinks
|
<MovieDetailsLinks
|
||||||
tmdbId={tmdbId}
|
tmdbId={tmdbId}
|
||||||
imdbId={imdbId}
|
|
||||||
youTubeTrailerId={youTubeTrailerId}
|
youTubeTrailerId={youTubeTrailerId}
|
||||||
|
imdbId={imdbId}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
canFlip={true}
|
canFlip={true}
|
||||||
@@ -273,7 +237,6 @@ class AddNewMovieSearchResult extends Component {
|
|||||||
hasMovieFiles={hasFile}
|
hasMovieFiles={hasFile}
|
||||||
monitored={monitored}
|
monitored={monitored}
|
||||||
isAvailable={isAvailable}
|
isAvailable={isAvailable}
|
||||||
queueItem={queueItem}
|
|
||||||
id={id}
|
id={id}
|
||||||
useLabel={true}
|
useLabel={true}
|
||||||
colorImpairedMode={colorImpairedMode}
|
colorImpairedMode={colorImpairedMode}
|
||||||
@@ -310,30 +273,25 @@ AddNewMovieSearchResult.propTypes = {
|
|||||||
titleSlug: PropTypes.string.isRequired,
|
titleSlug: PropTypes.string.isRequired,
|
||||||
year: PropTypes.number.isRequired,
|
year: PropTypes.number.isRequired,
|
||||||
studio: PropTypes.string,
|
studio: PropTypes.string,
|
||||||
genres: PropTypes.arrayOf(PropTypes.string),
|
|
||||||
status: PropTypes.string.isRequired,
|
status: PropTypes.string.isRequired,
|
||||||
overview: PropTypes.string,
|
overview: PropTypes.string,
|
||||||
ratings: PropTypes.object.isRequired,
|
ratings: PropTypes.object.isRequired,
|
||||||
folder: PropTypes.string.isRequired,
|
folder: PropTypes.string.isRequired,
|
||||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
existingMovieId: PropTypes.number,
|
|
||||||
isExistingMovie: PropTypes.bool.isRequired,
|
isExistingMovie: PropTypes.bool.isRequired,
|
||||||
isExclusionMovie: PropTypes.bool.isRequired,
|
isExclusionMovie: PropTypes.bool.isRequired,
|
||||||
isSmallScreen: PropTypes.bool.isRequired,
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
id: PropTypes.number,
|
id: PropTypes.number,
|
||||||
|
queueItems: PropTypes.arrayOf(PropTypes.object),
|
||||||
monitored: PropTypes.bool.isRequired,
|
monitored: PropTypes.bool.isRequired,
|
||||||
hasFile: PropTypes.bool.isRequired,
|
hasFile: PropTypes.bool.isRequired,
|
||||||
isAvailable: PropTypes.bool.isRequired,
|
isAvailable: PropTypes.bool.isRequired,
|
||||||
movieFile: PropTypes.object,
|
|
||||||
queueItem: PropTypes.object,
|
|
||||||
colorImpairedMode: PropTypes.bool,
|
colorImpairedMode: PropTypes.bool,
|
||||||
|
queueStatus: PropTypes.string,
|
||||||
|
queueState: PropTypes.string,
|
||||||
runtime: PropTypes.number.isRequired,
|
runtime: PropTypes.number.isRequired,
|
||||||
movieRuntimeFormat: PropTypes.string.isRequired,
|
movieRuntimeFormat: PropTypes.string.isRequired,
|
||||||
certification: PropTypes.string
|
certification: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
AddNewMovieSearchResult.defaultProps = {
|
|
||||||
genres: []
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AddNewMovieSearchResult;
|
export default AddNewMovieSearchResult;
|
||||||
|
|||||||
@@ -12,17 +12,15 @@ function createMapStateToProps() {
|
|||||||
createDimensionsSelector(),
|
createDimensionsSelector(),
|
||||||
(state) => state.queue.details.items,
|
(state) => state.queue.details.items,
|
||||||
(state, { internalId }) => internalId,
|
(state, { internalId }) => internalId,
|
||||||
(state) => state.settings.ui.item.movieRuntimeFormat,
|
(isExistingMovie, isExclusionMovie, dimensions, queueItems, internalId) => {
|
||||||
(isExistingMovie, isExclusionMovie, dimensions, queueItems, internalId, movieRuntimeFormat) => {
|
const firstQueueItem = queueItems.find((q) => q.movieId === internalId && internalId > 0);
|
||||||
const queueItem = queueItems.find((item) => internalId > 0 && item.movieId === internalId);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
existingMovieId: internalId,
|
|
||||||
isExistingMovie,
|
isExistingMovie,
|
||||||
isExclusionMovie,
|
isExclusionMovie,
|
||||||
isSmallScreen: dimensions.isSmallScreen,
|
isSmallScreen: dimensions.isSmallScreen,
|
||||||
queueItem,
|
queueStatus: firstQueueItem ? firstQueueItem.status : null,
|
||||||
movieRuntimeFormat
|
queueState: firstQueueItem ? firstQueueItem.trackedDownloadState : null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
.contentContainer {
|
.contentContainer {
|
||||||
z-index: $popperZIndex;
|
z-index: $popperZIndex;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
/* 400px container width with 8px padding on each side */
|
/* 400px container witdh with 8px padding on each side */
|
||||||
width: 384px;
|
width: 384px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ class ImportMovieSelectFolder extends Component {
|
|||||||
className={styles.addErrorAlert}
|
className={styles.addErrorAlert}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
>
|
>
|
||||||
{translate('AddRootFolderError')}
|
{translate('UnableToAddRootFolder')}
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,13 +5,12 @@ import React, { Component } from 'react';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { addRootFolder, deleteRootFolder, fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
import { addRootFolder, deleteRootFolder, fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||||
import createRootFoldersSelector from 'Store/Selectors/createRootFoldersSelector';
|
|
||||||
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
|
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
|
||||||
import ImportMovieSelectFolder from './ImportMovieSelectFolder';
|
import ImportMovieSelectFolder from './ImportMovieSelectFolder';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createRootFoldersSelector(),
|
(state) => state.rootFolders,
|
||||||
createSystemStatusSelector(),
|
createSystemStatusSelector(),
|
||||||
(rootFolders, systemStatus) => {
|
(rootFolders, systemStatus) => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -65,12 +65,12 @@ function AppUpdatedModalContent(props) {
|
|||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
{translate('AppUpdated')}
|
{translate('AppUpdated', { appName: 'Radarr' })}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<div>
|
<div>
|
||||||
<InlineMarkdown data={translate('AppUpdatedVersion', { version })} blockClassName={styles.version} />
|
<InlineMarkdown data={translate('AppUpdatedVersion', { appName: 'Radarr', version })} blockClassName={styles.version} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,11 +28,11 @@ function ConnectionLostModal(props) {
|
|||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<div>
|
<div>
|
||||||
{translate('ConnectionLostToBackend')}
|
{translate('ConnectionLostToBackend', { appName: 'Radarr' })}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.automatic}>
|
<div className={styles.automatic}>
|
||||||
{translate('ConnectionLostReconnect')}
|
{translate('ConnectionLostReconnect', { appName: 'Radarr' })}
|
||||||
</div>
|
</div>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
|
|||||||
@@ -44,16 +44,7 @@ export interface CustomFilter {
|
|||||||
filers: PropertyFilter[];
|
filers: PropertyFilter[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AppSectionState {
|
|
||||||
dimensions: {
|
|
||||||
isSmallScreen: boolean;
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AppState {
|
interface AppState {
|
||||||
app: AppSectionState;
|
|
||||||
calendar: CalendarAppState;
|
calendar: CalendarAppState;
|
||||||
commands: CommandAppState;
|
commands: CommandAppState;
|
||||||
history: HistoryAppState;
|
history: HistoryAppState;
|
||||||
|
|||||||
@@ -1,10 +1,40 @@
|
|||||||
import Queue from 'typings/Queue';
|
import ModelBase from 'App/ModelBase';
|
||||||
|
import Language from 'Language/Language';
|
||||||
|
import { QualityModel } from 'Quality/Quality';
|
||||||
|
import CustomFormat from 'typings/CustomFormat';
|
||||||
import AppSectionState, {
|
import AppSectionState, {
|
||||||
AppSectionFilterState,
|
AppSectionFilterState,
|
||||||
AppSectionItemState,
|
AppSectionItemState,
|
||||||
Error,
|
Error,
|
||||||
} from './AppSectionState';
|
} from './AppSectionState';
|
||||||
|
|
||||||
|
export interface StatusMessage {
|
||||||
|
title: string;
|
||||||
|
messages: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Queue extends ModelBase {
|
||||||
|
languages: Language[];
|
||||||
|
quality: QualityModel;
|
||||||
|
customFormats: CustomFormat[];
|
||||||
|
size: number;
|
||||||
|
title: string;
|
||||||
|
sizeleft: number;
|
||||||
|
timeleft: string;
|
||||||
|
estimatedCompletionTime: string;
|
||||||
|
status: string;
|
||||||
|
trackedDownloadStatus: string;
|
||||||
|
trackedDownloadState: string;
|
||||||
|
statusMessages: StatusMessage[];
|
||||||
|
errorMessage: string;
|
||||||
|
downloadId: string;
|
||||||
|
protocol: string;
|
||||||
|
downloadClient: string;
|
||||||
|
outputPath: string;
|
||||||
|
movieHasFile: boolean;
|
||||||
|
movieId?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface QueueDetailsAppState extends AppSectionState<Queue> {
|
export interface QueueDetailsAppState extends AppSectionState<Queue> {
|
||||||
params: unknown;
|
params: unknown;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ class AgendaEvent extends Component {
|
|||||||
className={styles.statusIcon}
|
className={styles.statusIcon}
|
||||||
name={icons.MOVIE_FILE}
|
name={icons.MOVIE_FILE}
|
||||||
kind={kinds.WARNING}
|
kind={kinds.WARNING}
|
||||||
title={translate('QualityCutoffNotMet')}
|
title={translate('QualityCutoffHasNotBeenMet')}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class CalendarConnector extends Component {
|
|||||||
gotoCalendarToday
|
gotoCalendarToday
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
registerPagePopulator(this.repopulate, ['movieFileUpdated', 'movieFileDeleted']);
|
registerPagePopulator(this.repopulate);
|
||||||
|
|
||||||
if (useCurrentPage) {
|
if (useCurrentPage) {
|
||||||
fetchCalendar();
|
fetchCalendar();
|
||||||
|
|||||||
@@ -48,10 +48,6 @@ $fullColorGradient: rgba(244, 245, 246, 0.2);
|
|||||||
.statusContainer {
|
.statusContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
&:global(.fullColor) {
|
|
||||||
filter: var(--calendarFullColorFilter)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.statusIcon {
|
.statusIcon {
|
||||||
|
|||||||
@@ -76,18 +76,12 @@ class CalendarEvent extends Component {
|
|||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div className={styles.statusContainer}>
|
||||||
className={classNames(
|
|
||||||
styles.statusContainer,
|
|
||||||
fullColorEvents && 'fullColor'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{
|
{
|
||||||
queueItem ?
|
queueItem ?
|
||||||
<span className={styles.statusIcon}>
|
<span className={styles.statusIcon}>
|
||||||
<CalendarEventQueueDetails
|
<CalendarEventQueueDetails
|
||||||
{...queueItem}
|
{...queueItem}
|
||||||
fullColorEvents={fullColorEvents}
|
|
||||||
/>
|
/>
|
||||||
</span> :
|
</span> :
|
||||||
null
|
null
|
||||||
@@ -104,14 +98,12 @@ class CalendarEvent extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
showCutoffUnmetIcon &&
|
showCutoffUnmetIcon && !!movieFile && movieFile.qualityCutoffNotMet ?
|
||||||
!!movieFile &&
|
|
||||||
movieFile.qualityCutoffNotMet ?
|
|
||||||
<Icon
|
<Icon
|
||||||
className={styles.statusIcon}
|
className={styles.statusIcon}
|
||||||
name={icons.MOVIE_FILE}
|
name={icons.MOVIE_FILE}
|
||||||
kind={kinds.WARNING}
|
kind={kinds.WARNING}
|
||||||
title={translate('QualityCutoffNotMet')}
|
title={translate('QualityCutoffHasNotBeenMet')}
|
||||||
/> :
|
/> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,11 +20,10 @@ function Legend(props) {
|
|||||||
if (showCutoffUnmetIcon) {
|
if (showCutoffUnmetIcon) {
|
||||||
iconsToShow.push(
|
iconsToShow.push(
|
||||||
<LegendIconItem
|
<LegendIconItem
|
||||||
name={translate('CutoffNotMet')}
|
name={translate('CutoffUnmet')}
|
||||||
icon={icons.MOVIE_FILE}
|
icon={icons.MOVIE_FILE}
|
||||||
kind={kinds.WARNING}
|
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
|
||||||
fullColorEvents={fullColorEvents}
|
tooltip={translate('QualityOrLangCutoffHasNotBeenMet')}
|
||||||
tooltip={translate('QualityCutoffNotMet')}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,4 @@
|
|||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
|
|
||||||
&:global(.fullColorEvents) {
|
|
||||||
filter: var(--calendarFullColorFilter)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import classNames from 'classnames';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
@@ -7,9 +6,9 @@ import styles from './LegendIconItem.css';
|
|||||||
function LegendIconItem(props) {
|
function LegendIconItem(props) {
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
fullColorEvents,
|
|
||||||
icon,
|
icon,
|
||||||
kind,
|
kind,
|
||||||
|
darken,
|
||||||
tooltip
|
tooltip
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@@ -19,11 +18,9 @@ function LegendIconItem(props) {
|
|||||||
title={tooltip}
|
title={tooltip}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
className={classNames(
|
className={styles.icon}
|
||||||
styles.icon,
|
|
||||||
fullColorEvents && 'fullColorEvents'
|
|
||||||
)}
|
|
||||||
name={icon}
|
name={icon}
|
||||||
|
darken={darken}
|
||||||
kind={kind}
|
kind={kind}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -34,10 +31,14 @@ function LegendIconItem(props) {
|
|||||||
|
|
||||||
LegendIconItem.propTypes = {
|
LegendIconItem.propTypes = {
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
fullColorEvents: PropTypes.bool.isRequired,
|
|
||||||
icon: PropTypes.object.isRequired,
|
icon: PropTypes.object.isRequired,
|
||||||
kind: PropTypes.string.isRequired,
|
kind: PropTypes.string.isRequired,
|
||||||
|
darken: PropTypes.bool.isRequired,
|
||||||
tooltip: PropTypes.string.isRequired
|
tooltip: PropTypes.string.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
LegendIconItem.defaultProps = {
|
||||||
|
darken: false
|
||||||
|
};
|
||||||
|
|
||||||
export default LegendIconItem;
|
export default LegendIconItem;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import * as commandNames from 'Commands/commandNames';
|
|||||||
import withScrollPosition from 'Components/withScrollPosition';
|
import withScrollPosition from 'Components/withScrollPosition';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
import { saveMovieCollections, setMovieCollectionsFilter, setMovieCollectionsSort } from 'Store/Actions/movieCollectionActions';
|
import { saveMovieCollections, setMovieCollectionsFilter, setMovieCollectionsSort } from 'Store/Actions/movieCollectionActions';
|
||||||
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
|
|
||||||
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||||
import scrollPositions from 'Store/scrollPositions';
|
import scrollPositions from 'Store/scrollPositions';
|
||||||
import createCollectionClientSideCollectionItemsSelector from 'Store/Selectors/createCollectionClientSideCollectionItemsSelector';
|
import createCollectionClientSideCollectionItemsSelector from 'Store/Selectors/createCollectionClientSideCollectionItemsSelector';
|
||||||
@@ -39,12 +38,6 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
dispatchFetchRootFolders() {
|
dispatchFetchRootFolders() {
|
||||||
dispatch(fetchRootFolders());
|
dispatch(fetchRootFolders());
|
||||||
},
|
},
|
||||||
dispatchFetchQueueDetails() {
|
|
||||||
dispatch(fetchQueueDetails());
|
|
||||||
},
|
|
||||||
dispatchClearQueueDetails() {
|
|
||||||
dispatch(clearQueueDetails());
|
|
||||||
},
|
|
||||||
onUpdateSelectedPress(payload) {
|
onUpdateSelectedPress(payload) {
|
||||||
dispatch(saveMovieCollections(payload));
|
dispatch(saveMovieCollections(payload));
|
||||||
},
|
},
|
||||||
@@ -70,12 +63,10 @@ class CollectionConnector extends Component {
|
|||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
registerPagePopulator(this.repopulate);
|
registerPagePopulator(this.repopulate);
|
||||||
this.props.dispatchFetchRootFolders();
|
this.props.dispatchFetchRootFolders();
|
||||||
this.props.dispatchFetchQueueDetails();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
unregisterPagePopulator(this.repopulate);
|
unregisterPagePopulator(this.repopulate);
|
||||||
this.props.dispatchClearQueueDetails();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -108,9 +99,7 @@ CollectionConnector.propTypes = {
|
|||||||
isSmallScreen: PropTypes.bool.isRequired,
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
view: PropTypes.string.isRequired,
|
view: PropTypes.string.isRequired,
|
||||||
onUpdateSelectedPress: PropTypes.func.isRequired,
|
onUpdateSelectedPress: PropTypes.func.isRequired,
|
||||||
dispatchFetchRootFolders: PropTypes.func.isRequired,
|
dispatchFetchRootFolders: PropTypes.func.isRequired
|
||||||
dispatchFetchQueueDetails: PropTypes.func.isRequired,
|
|
||||||
dispatchClearQueueDetails: PropTypes.func.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withScrollPosition(
|
export default withScrollPosition(
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ class CollectionMovie extends Component {
|
|||||||
hasFile,
|
hasFile,
|
||||||
folder,
|
folder,
|
||||||
isAvailable,
|
isAvailable,
|
||||||
movieFile,
|
|
||||||
isExistingMovie,
|
isExistingMovie,
|
||||||
posterWidth,
|
posterWidth,
|
||||||
posterHeight,
|
posterHeight,
|
||||||
@@ -132,8 +131,6 @@ class CollectionMovie extends Component {
|
|||||||
id ?
|
id ?
|
||||||
<div className={styles.overlayStatus}>
|
<div className={styles.overlayStatus}>
|
||||||
<MovieIndexProgressBar
|
<MovieIndexProgressBar
|
||||||
movieId={id}
|
|
||||||
movieFile={movieFile}
|
|
||||||
monitored={monitored}
|
monitored={monitored}
|
||||||
hasFile={hasFile}
|
hasFile={hasFile}
|
||||||
status={status}
|
status={status}
|
||||||
@@ -183,7 +180,6 @@ CollectionMovie.propTypes = {
|
|||||||
hasFile: PropTypes.bool,
|
hasFile: PropTypes.bool,
|
||||||
folder: PropTypes.string,
|
folder: PropTypes.string,
|
||||||
isAvailable: PropTypes.bool,
|
isAvailable: PropTypes.bool,
|
||||||
movieFile: PropTypes.object,
|
|
||||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
posterWidth: PropTypes.number.isRequired,
|
posterWidth: PropTypes.number.isRequired,
|
||||||
posterHeight: PropTypes.number.isRequired,
|
posterHeight: PropTypes.number.isRequired,
|
||||||
|
|||||||
@@ -74,7 +74,11 @@ CollectionMovieLabel.propTypes = {
|
|||||||
|
|
||||||
CollectionMovieLabel.defaultProps = {
|
CollectionMovieLabel.defaultProps = {
|
||||||
isSaving: false,
|
isSaving: false,
|
||||||
statistics: {}
|
statistics: {
|
||||||
|
episodeFileCount: 0,
|
||||||
|
totalEpisodeCount: 0,
|
||||||
|
percentOfEpisodes: 0
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CollectionMovieLabel;
|
export default CollectionMovieLabel;
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
.description {
|
||||||
|
line-height: $lineHeight;
|
||||||
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
line-height: $lineHeight;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
|
|||||||
@@ -23,19 +23,19 @@ const EVENT_TYPE_OPTIONS = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 6,
|
id: 5,
|
||||||
get name() {
|
get name() {
|
||||||
return translate('Deleted');
|
return translate('Deleted');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 8,
|
id: 6,
|
||||||
get name() {
|
get name() {
|
||||||
return translate('Renamed');
|
return translate('Renamed');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 9,
|
id: 7,
|
||||||
get name() {
|
get name() {
|
||||||
return translate('Ignored');
|
return translate('Ignored');
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,24 +30,22 @@ function CustomFiltersModalContent(props) {
|
|||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
{
|
{
|
||||||
customFilters
|
customFilters.map((customFilter) => {
|
||||||
.sort((a, b) => a.label.localeCompare(b.label))
|
return (
|
||||||
.map((customFilter) => {
|
<CustomFilter
|
||||||
return (
|
key={customFilter.id}
|
||||||
<CustomFilter
|
id={customFilter.id}
|
||||||
key={customFilter.id}
|
label={customFilter.label}
|
||||||
id={customFilter.id}
|
filters={customFilter.filters}
|
||||||
label={customFilter.label}
|
selectedFilterKey={selectedFilterKey}
|
||||||
filters={customFilter.filters}
|
isDeleting={isDeleting}
|
||||||
selectedFilterKey={selectedFilterKey}
|
deleteError={deleteError}
|
||||||
isDeleting={isDeleting}
|
dispatchSetFilter={dispatchSetFilter}
|
||||||
deleteError={deleteError}
|
dispatchDeleteCustomFilter={dispatchDeleteCustomFilter}
|
||||||
dispatchSetFilter={dispatchSetFilter}
|
onEditPress={onEditCustomFilter}
|
||||||
dispatchDeleteCustomFilter={dispatchDeleteCustomFilter}
|
/>
|
||||||
onEditPress={onEditCustomFilter}
|
);
|
||||||
/>
|
})
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
<div className={styles.addButtonContainer}>
|
<div className={styles.addButtonContainer}>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import React, { Component } from 'react';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { addRootFolder } from 'Store/Actions/rootFolderActions';
|
import { addRootFolder } from 'Store/Actions/rootFolderActions';
|
||||||
import createRootFoldersSelector from 'Store/Selectors/createRootFoldersSelector';
|
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import RootFolderSelectInput from './RootFolderSelectInput';
|
import RootFolderSelectInput from './RootFolderSelectInput';
|
||||||
|
|
||||||
@@ -11,7 +10,7 @@ const ADD_NEW_KEY = 'addNew';
|
|||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createRootFoldersSelector(),
|
(state) => state.rootFolders,
|
||||||
(state, { value }) => value,
|
(state, { value }) => value,
|
||||||
(state, { includeMissingValue }) => includeMissingValue,
|
(state, { includeMissingValue }) => includeMissingValue,
|
||||||
(state, { includeNoChange }) => includeNoChange,
|
(state, { includeNoChange }) => includeNoChange,
|
||||||
|
|||||||
@@ -12,10 +12,18 @@
|
|||||||
|
|
||||||
.info {
|
.info {
|
||||||
color: var(--infoColor);
|
color: var(--infoColor);
|
||||||
|
|
||||||
|
&:global(.darken) {
|
||||||
|
color: color(var(--infoColor) shade(30%));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pink {
|
.pink {
|
||||||
color: var(--pink);
|
color: var(--pink);
|
||||||
|
|
||||||
|
&:global(.darken) {
|
||||||
|
color: color(var(--pink) shade(30%));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.success {
|
.success {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ class Icon extends PureComponent {
|
|||||||
kind,
|
kind,
|
||||||
size,
|
size,
|
||||||
title,
|
title,
|
||||||
|
darken,
|
||||||
isSpinning,
|
isSpinning,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
@@ -26,7 +27,8 @@ class Icon extends PureComponent {
|
|||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
styles[kind]
|
styles[kind],
|
||||||
|
darken && 'darken'
|
||||||
)}
|
)}
|
||||||
icon={name}
|
icon={name}
|
||||||
spin={isSpinning}
|
spin={isSpinning}
|
||||||
@@ -59,6 +61,7 @@ Icon.propTypes = {
|
|||||||
kind: PropTypes.string.isRequired,
|
kind: PropTypes.string.isRequired,
|
||||||
size: PropTypes.number.isRequired,
|
size: PropTypes.number.isRequired,
|
||||||
title: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
title: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
||||||
|
darken: PropTypes.bool.isRequired,
|
||||||
isSpinning: PropTypes.bool.isRequired,
|
isSpinning: PropTypes.bool.isRequired,
|
||||||
fixedWidth: PropTypes.bool.isRequired
|
fixedWidth: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
@@ -66,6 +69,7 @@ Icon.propTypes = {
|
|||||||
Icon.defaultProps = {
|
Icon.defaultProps = {
|
||||||
kind: kinds.DEFAULT,
|
kind: kinds.DEFAULT,
|
||||||
size: 14,
|
size: 14,
|
||||||
|
darken: false,
|
||||||
isSpinning: false,
|
isSpinning: false,
|
||||||
fixedWidth: false
|
fixedWidth: false
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -40,26 +40,18 @@ class FilterMenuContent extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
customFilters.length > 0 ?
|
customFilters.map((filter) => {
|
||||||
<MenuItemSeparator /> :
|
return (
|
||||||
null
|
<FilterMenuItem
|
||||||
}
|
key={filter.id}
|
||||||
|
filterKey={filter.id}
|
||||||
{
|
selectedFilterKey={selectedFilterKey}
|
||||||
customFilters
|
onPress={onFilterSelect}
|
||||||
.sort((a, b) => a.label.localeCompare(b.label))
|
>
|
||||||
.map((filter) => {
|
{filter.label}
|
||||||
return (
|
</FilterMenuItem>
|
||||||
<FilterMenuItem
|
);
|
||||||
key={filter.id}
|
})
|
||||||
filterKey={filter.id}
|
|
||||||
selectedFilterKey={selectedFilterKey}
|
|
||||||
onPress={onFilterSelect}
|
|
||||||
>
|
|
||||||
{filter.label}
|
|
||||||
</FilterMenuItem>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -63,12 +63,6 @@
|
|||||||
width: 1280px;
|
width: 1280px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.extraExtraLarge {
|
|
||||||
composes: modal;
|
|
||||||
|
|
||||||
width: 1600px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width: $breakpointExtraLarge) {
|
@media only screen and (max-width: $breakpointExtraLarge) {
|
||||||
.modal.extraLarge {
|
.modal.extraLarge {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
@@ -96,8 +90,7 @@
|
|||||||
.modal.small,
|
.modal.small,
|
||||||
.modal.medium,
|
.modal.medium,
|
||||||
.modal.large,
|
.modal.large,
|
||||||
.modal.extraLarge,
|
.modal.extraLarge {
|
||||||
.modal.extraExtraLarge {
|
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// This file is automatically generated.
|
// This file is automatically generated.
|
||||||
// Please do not change this file!
|
// Please do not change this file!
|
||||||
interface CssExports {
|
interface CssExports {
|
||||||
'extraExtraLarge': string;
|
|
||||||
'extraLarge': string;
|
'extraLarge': string;
|
||||||
'large': string;
|
'large': string;
|
||||||
'medium': string;
|
'medium': string;
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ class SignalRConnector extends Component {
|
|||||||
const resource = body.resource;
|
const resource = body.resource;
|
||||||
const status = resource.status;
|
const status = resource.status;
|
||||||
|
|
||||||
// Both successful and failed commands need to be
|
// Both sucessful and failed commands need to be
|
||||||
// completed, otherwise they spin until they timeout.
|
// completed, otherwise they spin until they timeout.
|
||||||
|
|
||||||
if (status === 'completed' || status === 'failed') {
|
if (status === 'completed' || status === 'failed') {
|
||||||
@@ -187,8 +187,6 @@ class SignalRConnector extends Component {
|
|||||||
repopulatePage('movieFileUpdated');
|
repopulatePage('movieFileUpdated');
|
||||||
} else if (body.action === 'deleted') {
|
} else if (body.action === 'deleted') {
|
||||||
this.props.dispatchRemoveItem({ section, id: body.resource.id });
|
this.props.dispatchRemoveItem({ section, id: body.resource.id });
|
||||||
|
|
||||||
repopulatePage('movieFileDeleted');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -15,5 +15,5 @@
|
|||||||
"start_url": "../../../../",
|
"start_url": "../../../../",
|
||||||
"theme_color": "#3a3f51",
|
"theme_color": "#3a3f51",
|
||||||
"background_color": "#3a3f51",
|
"background_color": "#3a3f51",
|
||||||
"display": "minimal-ui"
|
"display": "standalone"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,120 +0,0 @@
|
|||||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
|
||||||
|
|
||||||
// This file contains some helpers for power users in a browser console
|
|
||||||
|
|
||||||
let hasWarned = false;
|
|
||||||
|
|
||||||
function checkActivationWarning() {
|
|
||||||
if (!hasWarned) {
|
|
||||||
console.log('Activated RadarrApi console helpers.');
|
|
||||||
console.warn('Be warned: There will be no further confirmation checks.');
|
|
||||||
hasWarned = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function attachAsyncActions(promise) {
|
|
||||||
promise.filter = function() {
|
|
||||||
const args = arguments;
|
|
||||||
const res = this.then((d) => d.filter(...args));
|
|
||||||
attachAsyncActions(res);
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
promise.map = function() {
|
|
||||||
const args = arguments;
|
|
||||||
const res = this.then((d) => d.map(...args));
|
|
||||||
attachAsyncActions(res);
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
promise.all = function() {
|
|
||||||
const res = this.then((d) => Promise.all(d));
|
|
||||||
attachAsyncActions(res);
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
promise.forEach = function(action) {
|
|
||||||
const res = this.then((d) => Promise.all(d.map(action)));
|
|
||||||
attachAsyncActions(res);
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class ResourceApi {
|
|
||||||
constructor(api, url) {
|
|
||||||
this.api = api;
|
|
||||||
this.url = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
single(id) {
|
|
||||||
return this.api.fetch(`${this.url}/${id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
all() {
|
|
||||||
return this.api.fetch(this.url);
|
|
||||||
}
|
|
||||||
|
|
||||||
filter(pred) {
|
|
||||||
return this.all().filter(pred);
|
|
||||||
}
|
|
||||||
|
|
||||||
update(resource) {
|
|
||||||
return this.api.fetch(`${this.url}/${resource.id}`, { method: 'PUT', data: resource });
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(resource) {
|
|
||||||
if (typeof resource === 'object' && resource !== null && resource.id) {
|
|
||||||
resource = resource.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!resource || !Number.isInteger(resource)) {
|
|
||||||
throw Error('Invalid resource', resource);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.api.fetch(`${this.url}/${resource}`, { method: 'DELETE' });
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch(url, options) {
|
|
||||||
return this.api.fetch(`${this.url}${url}`, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ConsoleApi {
|
|
||||||
constructor() {
|
|
||||||
this.movie = new ResourceApi(this, '/movie');
|
|
||||||
}
|
|
||||||
|
|
||||||
resource(url) {
|
|
||||||
return new ResourceApi(this, url);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch(url, options) {
|
|
||||||
checkActivationWarning();
|
|
||||||
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
const req = {
|
|
||||||
url,
|
|
||||||
method: options.method || 'GET'
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.data) {
|
|
||||||
req.dataType = 'json';
|
|
||||||
req.data = JSON.stringify(options.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
const promise = createAjaxRequest(req).request;
|
|
||||||
|
|
||||||
promise.fail((xhr) => {
|
|
||||||
console.error(`Failed to fetch ${url}`, xhr);
|
|
||||||
});
|
|
||||||
|
|
||||||
attachAsyncActions(promise);
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.RadarrApi = new ConsoleApi();
|
|
||||||
|
|
||||||
export default ConsoleApi;
|
|
||||||
@@ -78,8 +78,7 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
|
|
||||||
onImportListSyncPress() {
|
onImportListSyncPress() {
|
||||||
dispatch(executeCommand({
|
dispatch(executeCommand({
|
||||||
name: commandNames.IMPORT_LIST_SYNC,
|
name: commandNames.IMPORT_LIST_SYNC
|
||||||
commandFinished: this.dispatchFetchListMovies
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ $hoverScale: 1.05;
|
|||||||
.title {
|
.title {
|
||||||
@add-mixin truncate;
|
@add-mixin truncate;
|
||||||
|
|
||||||
background-color: var(--movieBackgroundColor);
|
background-color: #fafbfc;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: $smallFontSize;
|
font-size: $smallFontSize;
|
||||||
}
|
}
|
||||||
@@ -68,19 +68,6 @@ $hoverScale: 1.05;
|
|||||||
color: var(--white);
|
color: var(--white);
|
||||||
}
|
}
|
||||||
|
|
||||||
.existing {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 1;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-width: 25px 25px 0 0;
|
|
||||||
border-style: solid;
|
|
||||||
border-color: #37bc9b transparent transparent;
|
|
||||||
color: var(--white);
|
|
||||||
}
|
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 10px;
|
bottom: 10px;
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ interface CssExports {
|
|||||||
'controls': string;
|
'controls': string;
|
||||||
'editorSelect': string;
|
'editorSelect': string;
|
||||||
'excluded': string;
|
'excluded': string;
|
||||||
'existing': string;
|
|
||||||
'externalLinks': string;
|
'externalLinks': string;
|
||||||
'link': string;
|
'link': string;
|
||||||
'overlayTitle': string;
|
'overlayTitle': string;
|
||||||
|
|||||||
@@ -92,7 +92,6 @@ class DiscoverMoviePoster extends Component {
|
|||||||
showRelativeDates,
|
showRelativeDates,
|
||||||
shortDateFormat,
|
shortDateFormat,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
movieRuntimeFormat,
|
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@@ -111,7 +110,7 @@ class DiscoverMoviePoster extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<div className={styles.posterContainer} title={title}>
|
<div className={styles.posterContainer}>
|
||||||
{
|
{
|
||||||
<div className={styles.editorSelect}>
|
<div className={styles.editorSelect}>
|
||||||
<CheckInput
|
<CheckInput
|
||||||
@@ -159,14 +158,6 @@ class DiscoverMoviePoster extends Component {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
isExisting &&
|
|
||||||
<div
|
|
||||||
className={styles.existing}
|
|
||||||
title={translate('Existing')}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
className={styles.link}
|
className={styles.link}
|
||||||
style={elementStyle}
|
style={elementStyle}
|
||||||
@@ -194,7 +185,7 @@ class DiscoverMoviePoster extends Component {
|
|||||||
|
|
||||||
{
|
{
|
||||||
showTitle &&
|
showTitle &&
|
||||||
<div className={styles.title} title={title}>
|
<div className={styles.title}>
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -203,7 +194,6 @@ class DiscoverMoviePoster extends Component {
|
|||||||
showRelativeDates={showRelativeDates}
|
showRelativeDates={showRelativeDates}
|
||||||
shortDateFormat={shortDateFormat}
|
shortDateFormat={shortDateFormat}
|
||||||
timeFormat={timeFormat}
|
timeFormat={timeFormat}
|
||||||
movieRuntimeFormat={movieRuntimeFormat}
|
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -246,7 +236,6 @@ DiscoverMoviePoster.propTypes = {
|
|||||||
showRelativeDates: PropTypes.bool.isRequired,
|
showRelativeDates: PropTypes.bool.isRequired,
|
||||||
shortDateFormat: PropTypes.string.isRequired,
|
shortDateFormat: PropTypes.string.isRequired,
|
||||||
timeFormat: PropTypes.string.isRequired,
|
timeFormat: PropTypes.string.isRequired,
|
||||||
movieRuntimeFormat: PropTypes.string.isRequired,
|
|
||||||
isExisting: PropTypes.bool.isRequired,
|
isExisting: PropTypes.bool.isRequired,
|
||||||
isExcluded: PropTypes.bool.isRequired,
|
isExcluded: PropTypes.bool.isRequired,
|
||||||
isSelected: PropTypes.bool,
|
isSelected: PropTypes.bool,
|
||||||
|
|||||||
@@ -5,11 +5,9 @@ import DiscoverMoviePoster from './DiscoverMoviePoster';
|
|||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.settings.ui.item.movieRuntimeFormat,
|
|
||||||
createDimensionsSelector(),
|
createDimensionsSelector(),
|
||||||
(movieRuntimeFormat, dimensions) => {
|
( dimensions) => {
|
||||||
return {
|
return {
|
||||||
movieRuntimeFormat,
|
|
||||||
isSmallScreen: dimensions.isSmallScreen
|
isSmallScreen: dimensions.isSmallScreen
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
.info {
|
.info {
|
||||||
background-color: var(--movieBackgroundColor);
|
background-color: #fafbfc;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: $smallFontSize;
|
font-size: $smallFontSize;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Icon from 'Components/Icon';
|
|
||||||
import TmdbRating from 'Components/TmdbRating';
|
import TmdbRating from 'Components/TmdbRating';
|
||||||
import { icons } from 'Helpers/Props';
|
|
||||||
import { getMovieStatusDetails } from 'Movie/MovieStatus';
|
import { getMovieStatusDetails } from 'Movie/MovieStatus';
|
||||||
import formatRuntime from 'Utilities/Date/formatRuntime';
|
import formatRuntime from 'Utilities/Date/formatRuntime';
|
||||||
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
import styles from './DiscoverMoviePosterInfo.css';
|
import styles from './DiscoverMoviePosterInfo.css';
|
||||||
|
|
||||||
function DiscoverMoviePosterInfo(props) {
|
function DiscoverMoviePosterInfo(props) {
|
||||||
@@ -22,13 +19,12 @@ function DiscoverMoviePosterInfo(props) {
|
|||||||
sortKey,
|
sortKey,
|
||||||
showRelativeDates,
|
showRelativeDates,
|
||||||
shortDateFormat,
|
shortDateFormat,
|
||||||
timeFormat,
|
timeFormat
|
||||||
movieRuntimeFormat
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
if (sortKey === 'status' && status) {
|
if (sortKey === 'status' && status) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.info} title={translate('Status')}>
|
<div className={styles.info}>
|
||||||
{getMovieStatusDetails(status).title}
|
{getMovieStatusDetails(status).title}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -36,7 +32,7 @@ function DiscoverMoviePosterInfo(props) {
|
|||||||
|
|
||||||
if (sortKey === 'studio' && studio) {
|
if (sortKey === 'studio' && studio) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.info} title={translate('Studio')}>
|
<div className={styles.info}>
|
||||||
{studio}
|
{studio}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -54,8 +50,8 @@ function DiscoverMoviePosterInfo(props) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.info} title={translate('InCinemas')}>
|
<div className={styles.info}>
|
||||||
<Icon name={icons.IN_CINEMAS} /> {inCinemasDate}
|
{`In Cinemas ${inCinemasDate}`}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -72,8 +68,8 @@ function DiscoverMoviePosterInfo(props) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.info} title={translate('DigitalRelease')}>
|
<div className={styles.info}>
|
||||||
<Icon name={icons.MOVIE_FILE} /> {digitalReleaseDate}
|
{`Digital ${digitalReleaseDate}`}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -90,15 +86,15 @@ function DiscoverMoviePosterInfo(props) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.info} title={translate('PhysicalRelease')}>
|
<div className={styles.info}>
|
||||||
<Icon name={icons.DISC} /> {physicalReleaseDate}
|
{`Released ${physicalReleaseDate}`}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sortKey === 'certification' && certification) {
|
if (sortKey === 'certification' && certification) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.info} title={translate('Certification')}>
|
<div className={styles.info}>
|
||||||
{certification}
|
{certification}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -106,8 +102,8 @@ function DiscoverMoviePosterInfo(props) {
|
|||||||
|
|
||||||
if (sortKey === 'runtime' && runtime) {
|
if (sortKey === 'runtime' && runtime) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.info} title={translate('Runtime')}>
|
<div className={styles.info}>
|
||||||
{formatRuntime(runtime, movieRuntimeFormat)}
|
{formatRuntime(runtime)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -115,7 +111,9 @@ function DiscoverMoviePosterInfo(props) {
|
|||||||
if (sortKey === 'ratings' && ratings) {
|
if (sortKey === 'ratings' && ratings) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.info}>
|
<div className={styles.info}>
|
||||||
<TmdbRating ratings={ratings} />
|
<TmdbRating
|
||||||
|
ratings={ratings}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -135,8 +133,7 @@ DiscoverMoviePosterInfo.propTypes = {
|
|||||||
sortKey: PropTypes.string.isRequired,
|
sortKey: PropTypes.string.isRequired,
|
||||||
showRelativeDates: PropTypes.bool.isRequired,
|
showRelativeDates: PropTypes.bool.isRequired,
|
||||||
shortDateFormat: PropTypes.string.isRequired,
|
shortDateFormat: PropTypes.string.isRequired,
|
||||||
timeFormat: PropTypes.string.isRequired,
|
timeFormat: PropTypes.string.isRequired
|
||||||
movieRuntimeFormat: PropTypes.string.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DiscoverMoviePosterInfo;
|
export default DiscoverMoviePosterInfo;
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ class DiscoverMovieRow extends Component {
|
|||||||
ratings,
|
ratings,
|
||||||
popularity,
|
popularity,
|
||||||
certification,
|
certification,
|
||||||
movieRuntimeFormat,
|
|
||||||
collection,
|
collection,
|
||||||
columns,
|
columns,
|
||||||
isExisting,
|
isExisting,
|
||||||
@@ -231,7 +230,7 @@ class DiscoverMovieRow extends Component {
|
|||||||
key={name}
|
key={name}
|
||||||
className={styles[name]}
|
className={styles[name]}
|
||||||
>
|
>
|
||||||
{formatRuntime(runtime, movieRuntimeFormat)}
|
{formatRuntime(runtime)}
|
||||||
</VirtualTableRowCell>
|
</VirtualTableRowCell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -398,7 +397,6 @@ DiscoverMovieRow.propTypes = {
|
|||||||
popularity: PropTypes.number.isRequired,
|
popularity: PropTypes.number.isRequired,
|
||||||
certification: PropTypes.string,
|
certification: PropTypes.string,
|
||||||
collection: PropTypes.object,
|
collection: PropTypes.object,
|
||||||
movieRuntimeFormat: PropTypes.string.isRequired,
|
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
isExisting: PropTypes.bool.isRequired,
|
isExisting: PropTypes.bool.isRequired,
|
||||||
isExcluded: PropTypes.bool.isRequired,
|
isExcluded: PropTypes.bool.isRequired,
|
||||||
|
|||||||
@@ -5,11 +5,9 @@ import DiscoverMovieRow from './DiscoverMovieRow';
|
|||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.settings.ui.item.movieRuntimeFormat,
|
|
||||||
createDimensionsSelector(),
|
createDimensionsSelector(),
|
||||||
(movieRuntimeFormat, dimensions) => {
|
(dimensions) => {
|
||||||
return {
|
return {
|
||||||
movieRuntimeFormat,
|
|
||||||
isSmallScreen: dimensions.isSmallScreen
|
isSmallScreen: dimensions.isSmallScreen
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,8 +34,7 @@ function AuthenticationRequiredModalContent(props) {
|
|||||||
authenticationMethod,
|
authenticationMethod,
|
||||||
authenticationRequired,
|
authenticationRequired,
|
||||||
username,
|
username,
|
||||||
password,
|
password
|
||||||
passwordConfirmation
|
|
||||||
} = settings;
|
} = settings;
|
||||||
|
|
||||||
const authenticationEnabled = authenticationMethod && authenticationMethod.value !== 'none';
|
const authenticationEnabled = authenticationMethod && authenticationMethod.value !== 'none';
|
||||||
@@ -64,7 +63,7 @@ function AuthenticationRequiredModalContent(props) {
|
|||||||
className={styles.authRequiredAlert}
|
className={styles.authRequiredAlert}
|
||||||
kind={kinds.WARNING}
|
kind={kinds.WARNING}
|
||||||
>
|
>
|
||||||
{translate('AuthenticationRequiredWarning')}
|
{translate('AuthenticationRequiredWarning', { appName: 'Radarr' })}
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -77,7 +76,7 @@ function AuthenticationRequiredModalContent(props) {
|
|||||||
type={inputTypes.SELECT}
|
type={inputTypes.SELECT}
|
||||||
name="authenticationMethod"
|
name="authenticationMethod"
|
||||||
values={authenticationMethodOptions}
|
values={authenticationMethodOptions}
|
||||||
helpText={translate('AuthenticationMethodHelpText')}
|
helpText={translate('AuthenticationMethodHelpText', { appName: 'Radarr' })}
|
||||||
helpTextWarning={authenticationMethod.value === 'none' ? translate('AuthenticationMethodHelpTextWarning') : undefined}
|
helpTextWarning={authenticationMethod.value === 'none' ? translate('AuthenticationMethodHelpTextWarning') : undefined}
|
||||||
helpLink="https://wiki.servarr.com/radarr/faq#forced-authentication"
|
helpLink="https://wiki.servarr.com/radarr/faq#forced-authentication"
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
@@ -121,18 +120,6 @@ function AuthenticationRequiredModalContent(props) {
|
|||||||
{...password}
|
{...password}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>{translate('PasswordConfirmation')}</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.PASSWORD}
|
|
||||||
name="passwordConfirmation"
|
|
||||||
onChange={onInputChange}
|
|
||||||
helpTextWarning={passwordConfirmation?.value ? undefined : translate('AuthenticationRequiredPasswordConfirmationHelpTextWarning')}
|
|
||||||
{...passwordConfirmation}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
</div> :
|
</div> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ import {
|
|||||||
faEye as fasEye,
|
faEye as fasEye,
|
||||||
faFastBackward as fasFastBackward,
|
faFastBackward as fasFastBackward,
|
||||||
faFastForward as fasFastForward,
|
faFastForward as fasFastForward,
|
||||||
faFileCircleQuestion as fasFileCircleQuestion,
|
|
||||||
faFileExport as fasFileExport,
|
faFileExport as fasFileExport,
|
||||||
faFileInvoice as farFileInvoice,
|
faFileInvoice as farFileInvoice,
|
||||||
faFilm as fasFilm,
|
faFilm as fasFilm,
|
||||||
@@ -160,7 +159,6 @@ export const EXPORT = fasFileExport;
|
|||||||
export const EXTERNAL_LINK = fasExternalLinkAlt;
|
export const EXTERNAL_LINK = fasExternalLinkAlt;
|
||||||
export const FATAL = fasTimesCircle;
|
export const FATAL = fasTimesCircle;
|
||||||
export const FILE = farFile;
|
export const FILE = farFile;
|
||||||
export const FILE_MISSING = fasFileCircleQuestion;
|
|
||||||
export const FILM = fasFilm;
|
export const FILM = fasFilm;
|
||||||
export const FILTER = fasFilter;
|
export const FILTER = fasFilter;
|
||||||
export const FLAG = fasFlag;
|
export const FLAG = fasFlag;
|
||||||
|
|||||||
@@ -3,6 +3,5 @@ export const SMALL = 'small';
|
|||||||
export const MEDIUM = 'medium';
|
export const MEDIUM = 'medium';
|
||||||
export const LARGE = 'large';
|
export const LARGE = 'large';
|
||||||
export const EXTRA_LARGE = 'extraLarge';
|
export const EXTRA_LARGE = 'extraLarge';
|
||||||
export const EXTRA_EXTRA_LARGE = 'extraExtraLarge';
|
|
||||||
|
|
||||||
export const all = [EXTRA_SMALL, SMALL, MEDIUM, LARGE, EXTRA_LARGE, EXTRA_EXTRA_LARGE];
|
export const all = [EXTRA_SMALL, SMALL, MEDIUM, LARGE, EXTRA_LARGE];
|
||||||
|
|||||||
+28
-47
@@ -3,16 +3,13 @@ import React, { Fragment } from 'react';
|
|||||||
import Alert from 'Components/Alert';
|
import Alert from 'Components/Alert';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
|
||||||
import PageMenuButton from 'Components/Menu/PageMenuButton';
|
|
||||||
import Table from 'Components/Table/Table';
|
import Table from 'Components/Table/Table';
|
||||||
import TableBody from 'Components/Table/TableBody';
|
import TableBody from 'Components/Table/TableBody';
|
||||||
import { align, icons, kinds, sortDirections } from 'Helpers/Props';
|
import { icons, kinds, sortDirections } from 'Helpers/Props';
|
||||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import InteractiveSearchFilterModalConnector from './InteractiveSearchFilterModalConnector';
|
|
||||||
import InteractiveSearchRowConnector from './InteractiveSearchRowConnector';
|
import InteractiveSearchRowConnector from './InteractiveSearchRowConnector';
|
||||||
import styles from './InteractiveSearch.css';
|
import styles from './InteractiveSearchContent.css';
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@@ -27,6 +24,23 @@ const columns = [
|
|||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'releaseWeight',
|
||||||
|
label: React.createElement(Icon, { name: icons.DOWNLOAD }),
|
||||||
|
isSortable: true,
|
||||||
|
fixedSortDirection: sortDirections.ASCENDING,
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'rejections',
|
||||||
|
label: React.createElement(Icon, {
|
||||||
|
name: icons.DANGER,
|
||||||
|
title: () => translate('Rejections')
|
||||||
|
}),
|
||||||
|
isSortable: true,
|
||||||
|
fixedSortDirection: sortDirections.ASCENDING,
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'title',
|
name: 'title',
|
||||||
label: () => translate('Title'),
|
label: () => translate('Title'),
|
||||||
@@ -70,6 +84,12 @@ const columns = [
|
|||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'customFormat',
|
||||||
|
label: () => translate('Formats'),
|
||||||
|
isSortable: true,
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'customFormatScore',
|
name: 'customFormatScore',
|
||||||
label: React.createElement(Icon, {
|
label: React.createElement(Icon, {
|
||||||
@@ -87,27 +107,10 @@ const columns = [
|
|||||||
}),
|
}),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'releaseWeight',
|
|
||||||
label: React.createElement(Icon, { name: icons.DOWNLOAD }),
|
|
||||||
isSortable: true,
|
|
||||||
fixedSortDirection: sortDirections.ASCENDING,
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'rejections',
|
|
||||||
label: React.createElement(Icon, {
|
|
||||||
name: icons.DANGER,
|
|
||||||
title: () => translate('Rejections')
|
|
||||||
}),
|
|
||||||
isSortable: true,
|
|
||||||
fixedSortDirection: sortDirections.ASCENDING,
|
|
||||||
isVisible: true
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
function InteractiveSearch(props) {
|
function InteractiveSearchContent(props) {
|
||||||
const {
|
const {
|
||||||
searchPayload,
|
searchPayload,
|
||||||
isFetching,
|
isFetching,
|
||||||
@@ -115,36 +118,18 @@ function InteractiveSearch(props) {
|
|||||||
error,
|
error,
|
||||||
totalReleasesCount,
|
totalReleasesCount,
|
||||||
items,
|
items,
|
||||||
selectedFilterKey,
|
|
||||||
filters,
|
|
||||||
customFilters,
|
|
||||||
sortKey,
|
sortKey,
|
||||||
sortDirection,
|
sortDirection,
|
||||||
longDateFormat,
|
longDateFormat,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
onSortPress,
|
onSortPress,
|
||||||
onFilterSelect,
|
|
||||||
onGrabPress
|
onGrabPress
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const errorMessage = getErrorMessage(error);
|
const errorMessage = getErrorMessage(error);
|
||||||
const type = 'movies';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.filterMenuContainer}>
|
|
||||||
<FilterMenu
|
|
||||||
alignMenu={align.RIGHT}
|
|
||||||
selectedFilterKey={selectedFilterKey}
|
|
||||||
filters={filters}
|
|
||||||
customFilters={customFilters}
|
|
||||||
buttonComponent={PageMenuButton}
|
|
||||||
filterModalConnectorComponent={InteractiveSearchFilterModalConnector}
|
|
||||||
filterModalConnectorComponentProps={{ type }}
|
|
||||||
onFilterSelect={onFilterSelect}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{
|
{
|
||||||
isFetching ? <LoadingIndicator /> : null
|
isFetching ? <LoadingIndicator /> : null
|
||||||
}
|
}
|
||||||
@@ -218,23 +203,19 @@ function InteractiveSearch(props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
InteractiveSearch.propTypes = {
|
InteractiveSearchContent.propTypes = {
|
||||||
searchPayload: PropTypes.object.isRequired,
|
searchPayload: PropTypes.object.isRequired,
|
||||||
isFetching: PropTypes.bool.isRequired,
|
isFetching: PropTypes.bool.isRequired,
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
error: PropTypes.object,
|
error: PropTypes.object,
|
||||||
totalReleasesCount: PropTypes.number.isRequired,
|
totalReleasesCount: PropTypes.number.isRequired,
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
|
||||||
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
sortKey: PropTypes.string,
|
sortKey: PropTypes.string,
|
||||||
sortDirection: PropTypes.string,
|
sortDirection: PropTypes.string,
|
||||||
longDateFormat: PropTypes.string.isRequired,
|
longDateFormat: PropTypes.string.isRequired,
|
||||||
timeFormat: PropTypes.string.isRequired,
|
timeFormat: PropTypes.string.isRequired,
|
||||||
onSortPress: PropTypes.func.isRequired,
|
onSortPress: PropTypes.func.isRequired,
|
||||||
onFilterSelect: PropTypes.func.isRequired,
|
|
||||||
onGrabPress: PropTypes.func.isRequired
|
onGrabPress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default InteractiveSearch;
|
export default InteractiveSearchContent;
|
||||||
+12
-25
@@ -2,11 +2,10 @@ import PropTypes from 'prop-types';
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { clearMovieHistory, fetchMovieHistory } from 'Store/Actions/movieHistoryActions';
|
|
||||||
import * as releaseActions from 'Store/Actions/releaseActions';
|
import * as releaseActions from 'Store/Actions/releaseActions';
|
||||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||||
import InteractiveSearch from './InteractiveSearch';
|
import InteractiveSearchContent from './InteractiveSearchContent';
|
||||||
|
|
||||||
function createMapStateToProps(appState) {
|
function createMapStateToProps(appState) {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
@@ -30,12 +29,8 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
dispatch(releaseActions.fetchReleases(payload));
|
dispatch(releaseActions.fetchReleases(payload));
|
||||||
},
|
},
|
||||||
|
|
||||||
dispatchFetchMovieHistory({ movieId }) {
|
dispatchClearReleases(payload) {
|
||||||
dispatch(fetchMovieHistory({ movieId }));
|
dispatch(releaseActions.clearReleases(payload));
|
||||||
},
|
|
||||||
|
|
||||||
dispatchClearMovieHistory() {
|
|
||||||
dispatch(clearMovieHistory());
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onSortPress(sortKey, sortDirection) {
|
onSortPress(sortKey, sortDirection) {
|
||||||
@@ -43,7 +38,8 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
onFilterSelect(selectedFilterKey) {
|
onFilterSelect(selectedFilterKey) {
|
||||||
dispatch(releaseActions.setReleasesFilter({ selectedFilterKey }));
|
const action = releaseActions.setReleasesFilter;
|
||||||
|
dispatch(action({ selectedFilterKey }));
|
||||||
},
|
},
|
||||||
|
|
||||||
onGrabPress(payload) {
|
onGrabPress(payload) {
|
||||||
@@ -52,7 +48,7 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
class InteractiveSearchConnector extends Component {
|
class InteractiveSearchContentConnector extends Component {
|
||||||
|
|
||||||
//
|
//
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
@@ -61,8 +57,7 @@ class InteractiveSearchConnector extends Component {
|
|||||||
const {
|
const {
|
||||||
searchPayload,
|
searchPayload,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
dispatchFetchReleases,
|
dispatchFetchReleases
|
||||||
dispatchFetchMovieHistory
|
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
// If search results are not yet isPopulated fetch them,
|
// If search results are not yet isPopulated fetch them,
|
||||||
@@ -70,12 +65,6 @@ class InteractiveSearchConnector extends Component {
|
|||||||
if (!isPopulated) {
|
if (!isPopulated) {
|
||||||
dispatchFetchReleases(searchPayload);
|
dispatchFetchReleases(searchPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatchFetchMovieHistory(searchPayload);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.props.dispatchClearMovieHistory();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -84,26 +73,24 @@ class InteractiveSearchConnector extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
dispatchFetchReleases,
|
dispatchFetchReleases,
|
||||||
dispatchFetchMovieHistory,
|
dispatchClearReleases,
|
||||||
dispatchClearMovieHistory,
|
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<InteractiveSearch
|
<InteractiveSearchContent
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InteractiveSearchConnector.propTypes = {
|
InteractiveSearchContentConnector.propTypes = {
|
||||||
searchPayload: PropTypes.object.isRequired,
|
searchPayload: PropTypes.object.isRequired,
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
dispatchFetchReleases: PropTypes.func.isRequired,
|
dispatchFetchReleases: PropTypes.func.isRequired,
|
||||||
dispatchFetchMovieHistory: PropTypes.func.isRequired,
|
dispatchClearReleases: PropTypes.func.isRequired
|
||||||
dispatchClearMovieHistory: PropTypes.func.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(createMapStateToProps, createMapDispatchToProps)(InteractiveSearchConnector);
|
export default connect(createMapStateToProps, createMapDispatchToProps)(InteractiveSearchContentConnector);
|
||||||
@@ -4,7 +4,7 @@ import FilterMenu from 'Components/Menu/FilterMenu';
|
|||||||
import PageMenuButton from 'Components/Menu/PageMenuButton';
|
import PageMenuButton from 'Components/Menu/PageMenuButton';
|
||||||
import { align } from 'Helpers/Props';
|
import { align } from 'Helpers/Props';
|
||||||
import InteractiveSearchFilterModalConnector from './InteractiveSearchFilterModalConnector';
|
import InteractiveSearchFilterModalConnector from './InteractiveSearchFilterModalConnector';
|
||||||
import styles from './InteractiveSearch.css';
|
import styles from './InteractiveSearchContent.css';
|
||||||
|
|
||||||
function InteractiveSearchFilterMenu(props) {
|
function InteractiveSearchFilterMenu(props) {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -1,29 +1,23 @@
|
|||||||
.protocol {
|
.cell {
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
}
|
||||||
|
|
||||||
|
.protocol {
|
||||||
|
composes: cell;
|
||||||
|
|
||||||
width: 80px;
|
width: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.titleContent {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indexer {
|
.indexer {
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
composes: cell;
|
||||||
|
|
||||||
width: 85px;
|
width: 85px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quality,
|
.quality,
|
||||||
|
.customFormat,
|
||||||
.languages {
|
.languages {
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
composes: cell;
|
||||||
}
|
|
||||||
|
|
||||||
.quality {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.languages {
|
.languages {
|
||||||
@@ -31,7 +25,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.customFormatScore {
|
.customFormatScore {
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
composes: cell;
|
||||||
|
|
||||||
width: 55px;
|
width: 55px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -39,28 +33,31 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.rejected,
|
.rejected,
|
||||||
.indexerFlags,
|
.indexerFlags {
|
||||||
.download {
|
composes: cell;
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
|
||||||
|
|
||||||
width: 50px;
|
width: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.age,
|
.age,
|
||||||
.size {
|
.size {
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
composes: cell;
|
||||||
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.peers {
|
.peers {
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
composes: cell;
|
||||||
|
|
||||||
width: 75px;
|
width: 75px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.titleContent {
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
.history {
|
.history {
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
composes: cell;
|
||||||
|
|
||||||
width: 75px;
|
width: 75px;
|
||||||
}
|
}
|
||||||
@@ -70,7 +67,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.download {
|
.download {
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
composes: cell;
|
||||||
|
|
||||||
width: 80px;
|
width: 80px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
interface CssExports {
|
interface CssExports {
|
||||||
'age': string;
|
'age': string;
|
||||||
'blocklist': string;
|
'blocklist': string;
|
||||||
|
'cell': string;
|
||||||
|
'customFormat': string;
|
||||||
'customFormatScore': string;
|
'customFormatScore': string;
|
||||||
'download': string;
|
'download': string;
|
||||||
'downloadIcon': string;
|
'downloadIcon': string;
|
||||||
|
|||||||
@@ -133,9 +133,9 @@ function InteractiveSearchRow(props: InteractiveSearchRowProps) {
|
|||||||
longDateFormat,
|
longDateFormat,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
grabError,
|
grabError,
|
||||||
historyGrabbedData = {} as MovieHistory,
|
historyGrabbedData,
|
||||||
historyFailedData = {} as MovieHistory,
|
historyFailedData,
|
||||||
blocklistData = {} as MovieBlocklist,
|
blocklistData,
|
||||||
searchPayload,
|
searchPayload,
|
||||||
onGrabPress,
|
onGrabPress,
|
||||||
} = props;
|
} = props;
|
||||||
@@ -199,6 +199,53 @@ function InteractiveSearchRow(props: InteractiveSearchRowProps) {
|
|||||||
{formatAge(age, ageHours, ageMinutes)}
|
{formatAge(age, ageHours, ageMinutes)}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell className={styles.download}>
|
||||||
|
<SpinnerIconButton
|
||||||
|
name={getDownloadIcon(isGrabbing, isGrabbed, grabError)}
|
||||||
|
kind={getDownloadKind(isGrabbed, grabError)}
|
||||||
|
title={getDownloadTooltip(isGrabbing, isGrabbed, grabError)}
|
||||||
|
isSpinning={isGrabbing}
|
||||||
|
onPress={onGrabPressWrapper}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
className={styles.manualDownloadContent}
|
||||||
|
title={translate('OverrideAndAddToDownloadQueue')}
|
||||||
|
onPress={onOverridePress}
|
||||||
|
>
|
||||||
|
<div className={styles.manualDownloadContent}>
|
||||||
|
<Icon
|
||||||
|
className={styles.interactiveIcon}
|
||||||
|
name={icons.INTERACTIVE}
|
||||||
|
size={12}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Icon
|
||||||
|
className={styles.downloadIcon}
|
||||||
|
name={icons.CIRCLE_DOWN}
|
||||||
|
size={10}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell className={styles.rejected}>
|
||||||
|
{rejections.length ? (
|
||||||
|
<Popover
|
||||||
|
anchor={<Icon name={icons.DANGER} kind={kinds.DANGER} />}
|
||||||
|
title={translate('ReleaseRejected')}
|
||||||
|
body={
|
||||||
|
<ul>
|
||||||
|
{rejections.map((rejection, index) => {
|
||||||
|
return <li key={index}>{rejection}</li>;
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
position={tooltipPositions.RIGHT}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
<TableRowCell>
|
<TableRowCell>
|
||||||
<div className={styles.titleContent}>
|
<div className={styles.titleContent}>
|
||||||
<Link to={infoUrl} title={title}>
|
<Link to={infoUrl} title={title}>
|
||||||
@@ -266,7 +313,11 @@ function InteractiveSearchRow(props: InteractiveSearchRowProps) {
|
|||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
<TableRowCell className={styles.quality}>
|
<TableRowCell className={styles.quality}>
|
||||||
<MovieQuality quality={quality} showRevision={true} />
|
<MovieQuality quality={quality} />
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell className={styles.customFormat}>
|
||||||
|
<MovieFormats formats={customFormats} />
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
<TableRowCell className={styles.customFormatScore}>
|
<TableRowCell className={styles.customFormatScore}>
|
||||||
@@ -297,53 +348,6 @@ function InteractiveSearchRow(props: InteractiveSearchRowProps) {
|
|||||||
) : null}
|
) : null}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
<TableRowCell className={styles.rejected}>
|
|
||||||
{rejections.length ? (
|
|
||||||
<Popover
|
|
||||||
anchor={<Icon name={icons.DANGER} kind={kinds.DANGER} />}
|
|
||||||
title={translate('ReleaseRejected')}
|
|
||||||
body={
|
|
||||||
<ul>
|
|
||||||
{rejections.map((rejection, index) => {
|
|
||||||
return <li key={index}>{rejection}</li>;
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
}
|
|
||||||
position={tooltipPositions.LEFT}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell className={styles.download}>
|
|
||||||
<SpinnerIconButton
|
|
||||||
name={getDownloadIcon(isGrabbing, isGrabbed, grabError)}
|
|
||||||
kind={getDownloadKind(isGrabbed, grabError)}
|
|
||||||
title={getDownloadTooltip(isGrabbing, isGrabbed, grabError)}
|
|
||||||
isSpinning={isGrabbing}
|
|
||||||
onPress={onGrabPressWrapper}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Link
|
|
||||||
className={styles.manualDownloadContent}
|
|
||||||
title={translate('OverrideAndAddToDownloadQueue')}
|
|
||||||
onPress={onOverridePress}
|
|
||||||
>
|
|
||||||
<div className={styles.manualDownloadContent}>
|
|
||||||
<Icon
|
|
||||||
className={styles.interactiveIcon}
|
|
||||||
name={icons.INTERACTIVE}
|
|
||||||
size={12}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Icon
|
|
||||||
className={styles.downloadIcon}
|
|
||||||
name={icons.CIRCLE_DOWN}
|
|
||||||
size={10}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
isOpen={isConfirmGrabModalOpen}
|
isOpen={isConfirmGrabModalOpen}
|
||||||
kind={kinds.WARNING}
|
kind={kinds.WARNING}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import InteractiveSearchContentConnector from './InteractiveSearchContentConnector';
|
||||||
|
|
||||||
|
function InteractiveSearchTable(props) {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InteractiveSearchContentConnector
|
||||||
|
searchPayload={props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
InteractiveSearchTable.propTypes = {
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InteractiveSearchTable;
|
||||||
@@ -50,16 +50,12 @@ class DeleteMovieModalContent extends Component {
|
|||||||
title,
|
title,
|
||||||
path,
|
path,
|
||||||
hasFile,
|
hasFile,
|
||||||
statistics,
|
|
||||||
deleteOptions,
|
deleteOptions,
|
||||||
|
sizeOnDisk,
|
||||||
onModalClose,
|
onModalClose,
|
||||||
onDeleteOptionChange
|
onDeleteOptionChange
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
|
||||||
sizeOnDisk = 0
|
|
||||||
} = statistics;
|
|
||||||
|
|
||||||
const deleteFiles = this.state.deleteFiles;
|
const deleteFiles = this.state.deleteFiles;
|
||||||
const addImportExclusion = deleteOptions.addImportExclusion;
|
const addImportExclusion = deleteOptions.addImportExclusion;
|
||||||
|
|
||||||
@@ -155,16 +151,12 @@ class DeleteMovieModalContent extends Component {
|
|||||||
DeleteMovieModalContent.propTypes = {
|
DeleteMovieModalContent.propTypes = {
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
path: PropTypes.string.isRequired,
|
path: PropTypes.string.isRequired,
|
||||||
statistics: PropTypes.object.isRequired,
|
|
||||||
hasFile: PropTypes.bool.isRequired,
|
hasFile: PropTypes.bool.isRequired,
|
||||||
|
sizeOnDisk: PropTypes.number.isRequired,
|
||||||
deleteOptions: PropTypes.object.isRequired,
|
deleteOptions: PropTypes.object.isRequired,
|
||||||
onDeleteOptionChange: PropTypes.func.isRequired,
|
onDeleteOptionChange: PropTypes.func.isRequired,
|
||||||
onDeletePress: PropTypes.func.isRequired,
|
onDeletePress: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
DeleteMovieModalContent.defaultProps = {
|
|
||||||
statistics: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DeleteMovieModalContent;
|
export default DeleteMovieModalContent;
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
import Label from 'Components/Label';
|
||||||
|
import IconButton from 'Components/Link/IconButton';
|
||||||
|
import { icons } from 'Helpers/Props';
|
||||||
import MovieHeadshot from 'Movie/MovieHeadshot';
|
import MovieHeadshot from 'Movie/MovieHeadshot';
|
||||||
import EditImportListModalConnector from 'Settings/ImportLists/ImportLists/EditImportListModalConnector';
|
import EditImportListModalConnector from 'Settings/ImportLists/ImportLists/EditImportListModalConnector';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
import styles from '../MovieCreditPoster.css';
|
import styles from '../MovieCreditPoster.css';
|
||||||
|
|
||||||
class MovieCastPoster extends Component {
|
class MovieCastPoster extends Component {
|
||||||
@@ -57,7 +60,7 @@ class MovieCastPoster extends Component {
|
|||||||
images,
|
images,
|
||||||
posterWidth,
|
posterWidth,
|
||||||
posterHeight,
|
posterHeight,
|
||||||
importList
|
importListId
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -66,31 +69,36 @@ class MovieCastPoster extends Component {
|
|||||||
|
|
||||||
const elementStyle = {
|
const elementStyle = {
|
||||||
width: `${posterWidth}px`,
|
width: `${posterWidth}px`,
|
||||||
height: `${posterHeight}px`,
|
height: `${posterHeight}px`
|
||||||
borderRadius: '5px'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const contentStyle = {
|
const contentStyle = {
|
||||||
width: `${posterWidth}px`
|
width: `${posterWidth}px`
|
||||||
};
|
};
|
||||||
|
|
||||||
const monitored = importList !== undefined && importList.enabled && importList.enableAuto;
|
|
||||||
const importListId = importList ? importList.id : 0;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={styles.content}
|
className={styles.content}
|
||||||
style={contentStyle}
|
style={contentStyle}
|
||||||
>
|
>
|
||||||
<div className={styles.posterContainer}>
|
<div className={styles.posterContainer}>
|
||||||
<div className={styles.controls}>
|
<Label className={styles.controls}>
|
||||||
<MonitorToggleButton
|
{
|
||||||
className={styles.action}
|
importListId > 0 ?
|
||||||
monitored={monitored}
|
<IconButton
|
||||||
size={20}
|
className={styles.action}
|
||||||
onPress={importListId > 0 ? this.onEditImportListPress : this.onAddImportListPress}
|
name={icons.EDIT}
|
||||||
/>
|
title={translate('EditPerson')}
|
||||||
</div>
|
onPress={this.onEditImportListPress}
|
||||||
|
/> :
|
||||||
|
<IconButton
|
||||||
|
className={styles.action}
|
||||||
|
name={icons.ADD}
|
||||||
|
title={translate('FollowPerson')}
|
||||||
|
onPress={this.onAddImportListPress}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</Label>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={elementStyle}
|
style={elementStyle}
|
||||||
@@ -140,8 +148,12 @@ MovieCastPoster.propTypes = {
|
|||||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
posterWidth: PropTypes.number.isRequired,
|
posterWidth: PropTypes.number.isRequired,
|
||||||
posterHeight: PropTypes.number.isRequired,
|
posterHeight: PropTypes.number.isRequired,
|
||||||
importList: PropTypes.object,
|
importListId: PropTypes.number.isRequired,
|
||||||
onImportListSelect: PropTypes.func.isRequired
|
onImportListSelect: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MovieCastPoster.defaultProps = {
|
||||||
|
importListId: 0
|
||||||
|
};
|
||||||
|
|
||||||
export default MovieCastPoster;
|
export default MovieCastPoster;
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
import Label from 'Components/Label';
|
||||||
|
import IconButton from 'Components/Link/IconButton';
|
||||||
|
import { icons } from 'Helpers/Props';
|
||||||
import MovieHeadshot from 'Movie/MovieHeadshot';
|
import MovieHeadshot from 'Movie/MovieHeadshot';
|
||||||
import EditImportListModalConnector from 'Settings/ImportLists/ImportLists/EditImportListModalConnector';
|
import EditImportListModalConnector from 'Settings/ImportLists/ImportLists/EditImportListModalConnector';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
import styles from '../MovieCreditPoster.css';
|
import styles from '../MovieCreditPoster.css';
|
||||||
|
|
||||||
class MovieCrewPoster extends Component {
|
class MovieCrewPoster extends Component {
|
||||||
@@ -57,7 +60,7 @@ class MovieCrewPoster extends Component {
|
|||||||
images,
|
images,
|
||||||
posterWidth,
|
posterWidth,
|
||||||
posterHeight,
|
posterHeight,
|
||||||
importList
|
importListId
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -66,31 +69,36 @@ class MovieCrewPoster extends Component {
|
|||||||
|
|
||||||
const elementStyle = {
|
const elementStyle = {
|
||||||
width: `${posterWidth}px`,
|
width: `${posterWidth}px`,
|
||||||
height: `${posterHeight}px`,
|
height: `${posterHeight}px`
|
||||||
borderRadius: '5px'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const contentStyle = {
|
const contentStyle = {
|
||||||
width: `${posterWidth}px`
|
width: `${posterWidth}px`
|
||||||
};
|
};
|
||||||
|
|
||||||
const monitored = importList !== undefined && importList.enabled && importList.enableAuto;
|
|
||||||
const importListId = importList ? importList.id : 0;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={styles.content}
|
className={styles.content}
|
||||||
style={contentStyle}
|
style={contentStyle}
|
||||||
>
|
>
|
||||||
<div className={styles.posterContainer}>
|
<div className={styles.posterContainer}>
|
||||||
<div className={styles.controls}>
|
<Label className={styles.controls}>
|
||||||
<MonitorToggleButton
|
{
|
||||||
className={styles.action}
|
importListId > 0 ?
|
||||||
monitored={monitored}
|
<IconButton
|
||||||
size={20}
|
className={styles.action}
|
||||||
onPress={importListId > 0 ? this.onEditImportListPress : this.onAddImportListPress}
|
name={icons.EDIT}
|
||||||
/>
|
title={translate('EditPerson')}
|
||||||
</div>
|
onPress={this.onEditImportListPress}
|
||||||
|
/> :
|
||||||
|
<IconButton
|
||||||
|
className={styles.action}
|
||||||
|
name={icons.ADD}
|
||||||
|
title={translate('FollowPerson')}
|
||||||
|
onPress={this.onAddImportListPress}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</Label>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={elementStyle}
|
style={elementStyle}
|
||||||
@@ -140,8 +148,12 @@ MovieCrewPoster.propTypes = {
|
|||||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
posterWidth: PropTypes.number.isRequired,
|
posterWidth: PropTypes.number.isRequired,
|
||||||
posterHeight: PropTypes.number.isRequired,
|
posterHeight: PropTypes.number.isRequired,
|
||||||
importList: PropTypes.object,
|
importListId: PropTypes.number.isRequired,
|
||||||
onImportListSelect: PropTypes.func.isRequired
|
onImportListSelect: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MovieCrewPoster.defaultProps = {
|
||||||
|
importListId: 0
|
||||||
|
};
|
||||||
|
|
||||||
export default MovieCrewPoster;
|
export default MovieCrewPoster;
|
||||||
|
|||||||
@@ -5,29 +5,6 @@ import { createSelector } from 'reselect';
|
|||||||
import MovieCreditPosters from '../MovieCreditPosters';
|
import MovieCreditPosters from '../MovieCreditPosters';
|
||||||
import MovieCrewPoster from './MovieCrewPoster';
|
import MovieCrewPoster from './MovieCrewPoster';
|
||||||
|
|
||||||
function crewSort(a, b) {
|
|
||||||
const jobOrder = ['Director', 'Writer', 'Producer', 'Executive Producer', 'Director of Photography'];
|
|
||||||
|
|
||||||
const indexA = jobOrder.indexOf(a.job);
|
|
||||||
const indexB = jobOrder.indexOf(b.job);
|
|
||||||
|
|
||||||
if (indexA === -1 && indexB === -1) {
|
|
||||||
return 0;
|
|
||||||
} else if (indexA === -1) {
|
|
||||||
return 1;
|
|
||||||
} else if (indexB === -1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (indexA < indexB) {
|
|
||||||
return -1;
|
|
||||||
} else if (indexA > indexB) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.movieCredits.items,
|
(state) => state.movieCredits.items,
|
||||||
@@ -40,10 +17,8 @@ function createMapStateToProps() {
|
|||||||
return acc;
|
return acc;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const sortedCrew = crew.sort(crewSort);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: _.uniqBy(sortedCrew, 'personName')
|
items: crew
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
$hoverScale: 1.05;
|
$hoverScale: 1.05;
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
border-radius: '5px';
|
|
||||||
transition: all 200ms ease-in;
|
transition: all 200ms ease-in;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
box-shadow: 0 0 12px var(--black);
|
box-shadow: 0 0 12px var(--black);
|
||||||
transition: all 200ms ease-in;
|
transition: all 200ms ease-in;
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
opacity: 0.9;
|
||||||
|
transition: opacity 200ms linear 150ms;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,18 +50,22 @@ $hoverScale: 1.05;
|
|||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
bottom: 10px;
|
||||||
|
left: 10px;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #707070;
|
||||||
|
color: var(--white);
|
||||||
|
font-size: $smallFontSize;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action {
|
.action {
|
||||||
composes: toggleButton from '~Components/MonitorToggleButton.css';
|
composes: button from '~Components/Link/IconButton.css';
|
||||||
|
|
||||||
width: 25px;
|
|
||||||
color: var(--white);
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--iconButtonHoverLightColor);
|
color: var(--radarrYellow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,11 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import { selectImportListSchema, setImportListFieldValue, setImportListValue } from 'Store/Actions/settingsActions';
|
import { selectImportListSchema, setImportListFieldValue, setImportListValue } from 'Store/Actions/settingsActions';
|
||||||
import createMovieCreditListSelector from 'Store/Selectors/createMovieCreditListSelector';
|
import createMovieCreditListSelector from 'Store/Selectors/createMovieCreditListSelector';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createMovieCreditListSelector();
|
||||||
createMovieCreditListSelector(),
|
|
||||||
(importList) => {
|
|
||||||
return {
|
|
||||||
importList
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
@@ -28,7 +20,7 @@ class MovieCreditPosterConnector extends Component {
|
|||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onImportListSelect = () => {
|
onImportListSelect = () => {
|
||||||
this.props.selectImportListSchema({ implementation: 'TMDbPersonImport', implementationName: 'TMDb Person', presetName: undefined });
|
this.props.selectImportListSchema({ implementation: 'TMDbPersonImport', presetName: undefined });
|
||||||
this.props.setImportListFieldValue({ name: 'personId', value: this.props.tmdbId.toString() });
|
this.props.setImportListFieldValue({ name: 'personId', value: this.props.tmdbId.toString() });
|
||||||
this.props.setImportListValue({ name: 'name', value: `${this.props.personName} - ${this.props.tmdbId}` });
|
this.props.setImportListValue({ name: 'name', value: `${this.props.personName} - ${this.props.tmdbId}` });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,16 +2,6 @@
|
|||||||
flex: 1 0 auto;
|
flex: 1 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movie {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sliderContainer {
|
|
||||||
--swiper-navigation-color: var(--white);
|
|
||||||
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
interface CssExports {
|
interface CssExports {
|
||||||
'container': string;
|
'container': string;
|
||||||
'grid': string;
|
'grid': string;
|
||||||
'movie': string;
|
|
||||||
'sliderContainer': string;
|
|
||||||
}
|
}
|
||||||
export const cssExports: CssExports;
|
export const cssExports: CssExports;
|
||||||
export default cssExports;
|
export default cssExports;
|
||||||
|
|||||||
@@ -1,19 +1,34 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { Navigation } from 'swiper';
|
import { Grid, WindowScroller } from 'react-virtualized';
|
||||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
import Measure from 'Components/Measure';
|
||||||
import dimensions from 'Styles/Variables/dimensions';
|
import dimensions from 'Styles/Variables/dimensions';
|
||||||
|
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
|
||||||
import MovieCreditPosterConnector from './MovieCreditPosterConnector';
|
import MovieCreditPosterConnector from './MovieCreditPosterConnector';
|
||||||
import styles from './MovieCreditPosters.css';
|
import styles from './MovieCreditPosters.css';
|
||||||
|
|
||||||
// Import Swiper styles
|
|
||||||
import 'swiper/css';
|
|
||||||
import 'swiper/css/navigation';
|
|
||||||
|
|
||||||
// Poster container dimensions
|
// Poster container dimensions
|
||||||
const columnPadding = parseInt(dimensions.movieIndexColumnPadding);
|
const columnPadding = parseInt(dimensions.movieIndexColumnPadding);
|
||||||
const columnPaddingSmallScreen = parseInt(dimensions.movieIndexColumnPaddingSmallScreen);
|
const columnPaddingSmallScreen = parseInt(dimensions.movieIndexColumnPaddingSmallScreen);
|
||||||
|
|
||||||
|
const additionalColumnCount = {
|
||||||
|
small: 3,
|
||||||
|
medium: 2,
|
||||||
|
large: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
function calculateColumnWidth(width, posterSize, isSmallScreen) {
|
||||||
|
const maxiumColumnWidth = isSmallScreen ? 172 : 182;
|
||||||
|
const columns = Math.floor(width / maxiumColumnWidth);
|
||||||
|
const remainder = width % maxiumColumnWidth;
|
||||||
|
|
||||||
|
if (remainder === 0 && posterSize === 'large') {
|
||||||
|
return maxiumColumnWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.floor(width / (columns + additionalColumnCount[posterSize]));
|
||||||
|
}
|
||||||
|
|
||||||
function calculateRowHeight(posterHeight, isSmallScreen) {
|
function calculateRowHeight(posterHeight, isSmallScreen) {
|
||||||
const titleHeight = 19;
|
const titleHeight = 19;
|
||||||
const characterHeight = 19;
|
const characterHeight = 19;
|
||||||
@@ -28,6 +43,10 @@ function calculateRowHeight(posterHeight, isSmallScreen) {
|
|||||||
return heights.reduce((acc, height) => acc + height, 0);
|
return heights.reduce((acc, height) => acc + height, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function calculatePosterHeight(posterWidth) {
|
||||||
|
return Math.ceil((250 / 170) * posterWidth);
|
||||||
|
}
|
||||||
|
|
||||||
class MovieCreditPosters extends Component {
|
class MovieCreditPosters extends Component {
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -44,58 +63,162 @@ class MovieCreditPosters extends Component {
|
|||||||
posterHeight: 238,
|
posterHeight: 238,
|
||||||
rowHeight: calculateRowHeight(238, props.isSmallScreen)
|
rowHeight: calculateRowHeight(238, props.isSmallScreen)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this._isInitialized = false;
|
||||||
|
this._grid = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps, prevState) {
|
||||||
|
const {
|
||||||
|
items
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
width,
|
||||||
|
columnWidth,
|
||||||
|
columnCount,
|
||||||
|
rowHeight
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
if (this._grid &&
|
||||||
|
(prevState.width !== width ||
|
||||||
|
prevState.columnWidth !== columnWidth ||
|
||||||
|
prevState.columnCount !== columnCount ||
|
||||||
|
prevState.rowHeight !== rowHeight ||
|
||||||
|
hasDifferentItemsOrOrder(prevProps.items, items))) {
|
||||||
|
// recomputeGridSize also forces Grid to discard its cache of rendered cells
|
||||||
|
this._grid.recomputeGridSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Control
|
||||||
|
|
||||||
|
setGridRef = (ref) => {
|
||||||
|
this._grid = ref;
|
||||||
|
};
|
||||||
|
|
||||||
|
calculateGrid = (width = this.state.width, isSmallScreen) => {
|
||||||
|
|
||||||
|
const padding = isSmallScreen ? columnPaddingSmallScreen : columnPadding;
|
||||||
|
const columnWidth = calculateColumnWidth(width, 'small', isSmallScreen);
|
||||||
|
const columnCount = Math.max(Math.floor(width / columnWidth), 1);
|
||||||
|
const posterWidth = columnWidth - padding;
|
||||||
|
const posterHeight = calculatePosterHeight(posterWidth);
|
||||||
|
const rowHeight = calculateRowHeight(posterHeight, isSmallScreen);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
width,
|
||||||
|
columnWidth,
|
||||||
|
columnCount,
|
||||||
|
posterWidth,
|
||||||
|
posterHeight,
|
||||||
|
rowHeight
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
cellRenderer = ({ key, rowIndex, columnIndex, style }) => {
|
||||||
|
const {
|
||||||
|
items,
|
||||||
|
itemComponent
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
posterWidth,
|
||||||
|
posterHeight,
|
||||||
|
columnCount
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
const movieIdx = rowIndex * columnCount + columnIndex;
|
||||||
|
const movie = items[movieIdx];
|
||||||
|
|
||||||
|
if (!movie) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={styles.container}
|
||||||
|
key={key}
|
||||||
|
style={style}
|
||||||
|
>
|
||||||
|
<MovieCreditPosterConnector
|
||||||
|
key={movie.order}
|
||||||
|
component={itemComponent}
|
||||||
|
posterWidth={posterWidth}
|
||||||
|
posterHeight={posterHeight}
|
||||||
|
tmdbId={movie.personTmdbId}
|
||||||
|
personName={movie.personName}
|
||||||
|
job={movie.job}
|
||||||
|
character={movie.character}
|
||||||
|
images={movie.images}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onMeasure = ({ width }) => {
|
||||||
|
this.calculateGrid(width, this.props.isSmallScreen);
|
||||||
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
items,
|
items
|
||||||
itemComponent,
|
|
||||||
isSmallScreen
|
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
posterWidth,
|
width,
|
||||||
posterHeight,
|
columnWidth,
|
||||||
|
columnCount,
|
||||||
rowHeight
|
rowHeight
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
return (
|
const rowCount = Math.ceil(items.length / columnCount);
|
||||||
|
|
||||||
<div className={styles.sliderContainer}>
|
return (
|
||||||
<Swiper
|
<Measure
|
||||||
slidesPerView='auto'
|
whitelist={['width']}
|
||||||
spaceBetween={10}
|
onMeasure={this.onMeasure}
|
||||||
slidesPerGroup={isSmallScreen ? 1 : 3}
|
>
|
||||||
navigation={true}
|
<WindowScroller
|
||||||
loop={false}
|
scrollElement={undefined}
|
||||||
loopFillGroupWithBlank={true}
|
|
||||||
className="mySwiper"
|
|
||||||
modules={[Navigation]}
|
|
||||||
onInit={(swiper) => {
|
|
||||||
swiper.navigation.init();
|
|
||||||
swiper.navigation.update();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{items.map((credit) => (
|
{({ height, registerChild, onChildScroll, scrollTop }) => {
|
||||||
<SwiperSlide key={credit.id} style={{ width: posterWidth, height: rowHeight }}>
|
if (!height) {
|
||||||
<MovieCreditPosterConnector
|
return <div />;
|
||||||
key={credit.id}
|
}
|
||||||
component={itemComponent}
|
|
||||||
posterWidth={posterWidth}
|
return (
|
||||||
posterHeight={posterHeight}
|
<div ref={registerChild}>
|
||||||
tmdbId={credit.personTmdbId}
|
<Grid
|
||||||
personName={credit.personName}
|
ref={this.setGridRef}
|
||||||
job={credit.job}
|
className={styles.grid}
|
||||||
character={credit.character}
|
autoHeight={true}
|
||||||
images={credit.images}
|
height={height}
|
||||||
/>
|
columnCount={columnCount}
|
||||||
</SwiperSlide>
|
columnWidth={columnWidth}
|
||||||
))}
|
rowCount={rowCount}
|
||||||
</Swiper>
|
rowHeight={rowHeight}
|
||||||
</div>
|
width={width}
|
||||||
|
onScroll={onChildScroll}
|
||||||
|
scrollTop={scrollTop}
|
||||||
|
overscanRowCount={2}
|
||||||
|
cellRenderer={this.cellRenderer}
|
||||||
|
scrollToAlignment={'start'}
|
||||||
|
isScrollingOptOut={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</WindowScroller>
|
||||||
|
</Measure>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.alternateTitle {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import styles from './MovieAlternateTitles.css';
|
||||||
|
|
||||||
|
function MovieAlternateTitles({ alternateTitles }) {
|
||||||
|
return (
|
||||||
|
<ul>
|
||||||
|
{
|
||||||
|
alternateTitles.filter((x, i, a) => a.indexOf(x) === i).map((alternateTitle) => {
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
key={alternateTitle}
|
||||||
|
className={styles.alternateTitle}
|
||||||
|
>
|
||||||
|
{alternateTitle}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
MovieAlternateTitles.propTypes = {
|
||||||
|
alternateTitles: PropTypes.arrayOf(PropTypes.string).isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MovieAlternateTitles;
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
.header {
|
.header {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 425px;
|
height: 375px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.errorMessage {
|
.errorMessage {
|
||||||
@@ -39,11 +39,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.poster {
|
.poster {
|
||||||
z-index: 2;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-right: 35px;
|
margin-right: 35px;
|
||||||
width: 250px;
|
width: 217px;
|
||||||
height: 368px;
|
height: 319px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
|
||||||
import TextTruncate from 'react-text-truncate';
|
import TextTruncate from 'react-text-truncate';
|
||||||
import Alert from 'Components/Alert';
|
import Alert from 'Components/Alert';
|
||||||
import FieldSet from 'Components/FieldSet';
|
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import ImdbRating from 'Components/ImdbRating';
|
import ImdbRating from 'Components/ImdbRating';
|
||||||
import InfoLabel from 'Components/InfoLabel';
|
import InfoLabel from 'Components/InfoLabel';
|
||||||
@@ -23,11 +23,12 @@ import Popover from 'Components/Tooltip/Popover';
|
|||||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||||
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
|
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
|
||||||
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
||||||
|
import InteractiveSearchFilterMenuConnector from 'InteractiveSearch/InteractiveSearchFilterMenuConnector';
|
||||||
|
import InteractiveSearchTable from 'InteractiveSearch/InteractiveSearchTable';
|
||||||
import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal';
|
import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal';
|
||||||
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
||||||
import MovieHistoryModal from 'Movie/History/MovieHistoryModal';
|
import MovieHistoryTable from 'Movie/History/MovieHistoryTable';
|
||||||
import MoviePoster from 'Movie/MoviePoster';
|
import MoviePoster from 'Movie/MoviePoster';
|
||||||
import MovieInteractiveSearchModalConnector from 'Movie/Search/MovieInteractiveSearchModalConnector';
|
|
||||||
import MovieFileEditorTable from 'MovieFile/Editor/MovieFileEditorTable';
|
import MovieFileEditorTable from 'MovieFile/Editor/MovieFileEditorTable';
|
||||||
import ExtraFileTable from 'MovieFile/Extras/ExtraFileTable';
|
import ExtraFileTable from 'MovieFile/Extras/ExtraFileTable';
|
||||||
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
|
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
|
||||||
@@ -37,6 +38,8 @@ import * as keyCodes from 'Utilities/Constants/keyCodes';
|
|||||||
import formatRuntime from 'Utilities/Date/formatRuntime';
|
import formatRuntime from 'Utilities/Date/formatRuntime';
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
|
import selectAll from 'Utilities/Table/selectAll';
|
||||||
|
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||||
import MovieCollectionLabelConnector from './../MovieCollectionLabelConnector';
|
import MovieCollectionLabelConnector from './../MovieCollectionLabelConnector';
|
||||||
import MovieCastPostersConnector from './Credits/Cast/MovieCastPostersConnector';
|
import MovieCastPostersConnector from './Credits/Cast/MovieCastPostersConnector';
|
||||||
import MovieCrewPostersConnector from './Credits/Crew/MovieCrewPostersConnector';
|
import MovieCrewPostersConnector from './Credits/Crew/MovieCrewPostersConnector';
|
||||||
@@ -54,6 +57,14 @@ function getFanartUrl(images) {
|
|||||||
return _.find(images, { coverType: 'fanart' })?.url;
|
return _.find(images, { coverType: 'fanart' })?.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getExpandedState(newState) {
|
||||||
|
return {
|
||||||
|
allExpanded: newState.allSelected,
|
||||||
|
allCollapsed: newState.allUnselected,
|
||||||
|
expandedState: newState.selectedState
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
class MovieDetails extends Component {
|
class MovieDetails extends Component {
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -67,8 +78,10 @@ class MovieDetails extends Component {
|
|||||||
isEditMovieModalOpen: false,
|
isEditMovieModalOpen: false,
|
||||||
isDeleteMovieModalOpen: false,
|
isDeleteMovieModalOpen: false,
|
||||||
isInteractiveImportModalOpen: false,
|
isInteractiveImportModalOpen: false,
|
||||||
isInteractiveSearchModalOpen: false,
|
allExpanded: false,
|
||||||
isMovieHistoryModalOpen: false,
|
allCollapsed: false,
|
||||||
|
expandedState: {},
|
||||||
|
selectedTabIndex: 0,
|
||||||
overviewHeight: 0,
|
overviewHeight: 0,
|
||||||
titleWidth: 0
|
titleWidth: 0
|
||||||
};
|
};
|
||||||
@@ -101,6 +114,10 @@ class MovieDetails extends Component {
|
|||||||
this.setState({ isOrganizeModalOpen: false });
|
this.setState({ isOrganizeModalOpen: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onManageEpisodesPress = () => {
|
||||||
|
this.setState({ isManageEpisodesOpen: true });
|
||||||
|
};
|
||||||
|
|
||||||
onInteractiveImportPress = () => {
|
onInteractiveImportPress = () => {
|
||||||
this.setState({ isInteractiveImportModalOpen: true });
|
this.setState({ isInteractiveImportModalOpen: true });
|
||||||
};
|
};
|
||||||
@@ -117,14 +134,6 @@ class MovieDetails extends Component {
|
|||||||
this.setState({ isEditMovieModalOpen: false });
|
this.setState({ isEditMovieModalOpen: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
onInteractiveSearchPress = () => {
|
|
||||||
this.setState({ isInteractiveSearchModalOpen: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
onInteractiveSearchModalClose = () => {
|
|
||||||
this.setState({ isInteractiveSearchModalOpen: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
onDeleteMoviePress = () => {
|
onDeleteMoviePress = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
isEditMovieModalOpen: false,
|
isEditMovieModalOpen: false,
|
||||||
@@ -136,12 +145,27 @@ class MovieDetails extends Component {
|
|||||||
this.setState({ isDeleteMovieModalOpen: false });
|
this.setState({ isDeleteMovieModalOpen: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
onMovieHistoryPress = () => {
|
onExpandAllPress = () => {
|
||||||
this.setState({ isMovieHistoryModalOpen: true });
|
const {
|
||||||
|
allExpanded,
|
||||||
|
expandedState
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
this.setState(getExpandedState(selectAll(expandedState, !allExpanded)));
|
||||||
};
|
};
|
||||||
|
|
||||||
onMovieHistoryModalClose = () => {
|
onExpandPress = (seasonNumber, isExpanded) => {
|
||||||
this.setState({ isMovieHistoryModalOpen: false });
|
this.setState((state) => {
|
||||||
|
const convertedState = {
|
||||||
|
allSelected: state.allExpanded,
|
||||||
|
allUnselected: state.allCollapsed,
|
||||||
|
selectedState: state.expandedState
|
||||||
|
};
|
||||||
|
|
||||||
|
const newState = toggleSelected(convertedState, [], seasonNumber, isExpanded, false);
|
||||||
|
|
||||||
|
return getExpandedState(newState);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onMeasure = ({ height }) => {
|
onMeasure = ({ height }) => {
|
||||||
@@ -180,12 +204,7 @@ class MovieDetails extends Component {
|
|||||||
if (
|
if (
|
||||||
touchStart < 50 ||
|
touchStart < 50 ||
|
||||||
this.props.isSidebarVisible ||
|
this.props.isSidebarVisible ||
|
||||||
this.state.isOrganizeModalOpen ||
|
this.state.isEventModalOpen
|
||||||
this.state.isEditMovieModalOpen ||
|
|
||||||
this.state.isDeleteMovieModalOpen ||
|
|
||||||
this.state.isInteractiveImportModalOpen ||
|
|
||||||
this.state.isInteractiveSearchModalOpen ||
|
|
||||||
this.state.isMovieHistoryModalOpen
|
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -220,6 +239,10 @@ class MovieDetails extends Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onTabSelect = (index, lastIndex) => {
|
||||||
|
this.setState({ selectedTabIndex: index });
|
||||||
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
@@ -238,7 +261,7 @@ class MovieDetails extends Component {
|
|||||||
certification,
|
certification,
|
||||||
ratings,
|
ratings,
|
||||||
path,
|
path,
|
||||||
statistics,
|
sizeOnDisk,
|
||||||
qualityProfileId,
|
qualityProfileId,
|
||||||
monitored,
|
monitored,
|
||||||
studio,
|
studio,
|
||||||
@@ -263,23 +286,18 @@ class MovieDetails extends Component {
|
|||||||
onMonitorTogglePress,
|
onMonitorTogglePress,
|
||||||
onRefreshPress,
|
onRefreshPress,
|
||||||
onSearchPress,
|
onSearchPress,
|
||||||
queueItem,
|
queueItems,
|
||||||
movieRuntimeFormat
|
movieRuntimeFormat
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
|
||||||
sizeOnDisk = 0
|
|
||||||
} = statistics;
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isOrganizeModalOpen,
|
isOrganizeModalOpen,
|
||||||
isEditMovieModalOpen,
|
isEditMovieModalOpen,
|
||||||
isDeleteMovieModalOpen,
|
isDeleteMovieModalOpen,
|
||||||
isInteractiveImportModalOpen,
|
isInteractiveImportModalOpen,
|
||||||
isInteractiveSearchModalOpen,
|
|
||||||
isMovieHistoryModalOpen,
|
|
||||||
overviewHeight,
|
overviewHeight,
|
||||||
titleWidth
|
titleWidth,
|
||||||
|
selectedTabIndex
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const fanartUrl = getFanartUrl(images);
|
const fanartUrl = getFanartUrl(images);
|
||||||
@@ -306,14 +324,6 @@ class MovieDetails extends Component {
|
|||||||
onPress={onSearchPress}
|
onPress={onSearchPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PageToolbarButton
|
|
||||||
label={translate('InteractiveSearch')}
|
|
||||||
iconName={icons.INTERACTIVE}
|
|
||||||
isSpinning={isSearching}
|
|
||||||
title={undefined}
|
|
||||||
onPress={this.onInteractiveSearchPress}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PageToolbarSeparator />
|
<PageToolbarSeparator />
|
||||||
|
|
||||||
<PageToolbarButton
|
<PageToolbarButton
|
||||||
@@ -324,17 +334,11 @@ class MovieDetails extends Component {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<PageToolbarButton
|
<PageToolbarButton
|
||||||
label={translate('ManageFiles')}
|
label={translate('ManualImport')}
|
||||||
iconName={icons.MOVIE_FILE}
|
iconName={icons.INTERACTIVE}
|
||||||
onPress={this.onInteractiveImportPress}
|
onPress={this.onInteractiveImportPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PageToolbarButton
|
|
||||||
label={translate('History')}
|
|
||||||
iconName={icons.HISTORY}
|
|
||||||
onPress={this.onMovieHistoryPress}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PageToolbarSeparator />
|
<PageToolbarSeparator />
|
||||||
|
|
||||||
<PageToolbarButton
|
<PageToolbarButton
|
||||||
@@ -540,7 +544,7 @@ class MovieDetails extends Component {
|
|||||||
hasMovieFiles={hasMovieFiles}
|
hasMovieFiles={hasMovieFiles}
|
||||||
monitored={monitored}
|
monitored={monitored}
|
||||||
isAvailable={isAvailable}
|
isAvailable={isAvailable}
|
||||||
queueItem={queueItem}
|
queueItem={(queueItems.length > 0) ? queueItems[0] : null}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</InfoLabel>
|
</InfoLabel>
|
||||||
@@ -650,33 +654,101 @@ class MovieDetails extends Component {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
<FieldSet legend={translate('Files')}>
|
<Tabs selectedIndex={selectedTabIndex} onSelect={this.onTabSelect}>
|
||||||
<MovieFileEditorTable
|
<TabList
|
||||||
movieId={id}
|
className={styles.tabList}
|
||||||
/>
|
>
|
||||||
|
<Tab
|
||||||
|
className={styles.tab}
|
||||||
|
selectedClassName={styles.selectedTab}
|
||||||
|
>
|
||||||
|
{translate('History')}
|
||||||
|
</Tab>
|
||||||
|
|
||||||
<ExtraFileTable
|
<Tab
|
||||||
movieId={id}
|
className={styles.tab}
|
||||||
/>
|
selectedClassName={styles.selectedTab}
|
||||||
</FieldSet>
|
>
|
||||||
|
{translate('Search')}
|
||||||
|
</Tab>
|
||||||
|
|
||||||
<FieldSet legend={translate('Cast')}>
|
<Tab
|
||||||
<MovieCastPostersConnector
|
className={styles.tab}
|
||||||
isSmallScreen={isSmallScreen}
|
selectedClassName={styles.selectedTab}
|
||||||
/>
|
>
|
||||||
</FieldSet>
|
{translate('Files')}
|
||||||
|
</Tab>
|
||||||
|
|
||||||
<FieldSet legend={translate('Crew')}>
|
<Tab
|
||||||
<MovieCrewPostersConnector
|
className={styles.tab}
|
||||||
isSmallScreen={isSmallScreen}
|
selectedClassName={styles.selectedTab}
|
||||||
/>
|
>
|
||||||
</FieldSet>
|
{translate('Titles')}
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
<Tab
|
||||||
|
className={styles.tab}
|
||||||
|
selectedClassName={styles.selectedTab}
|
||||||
|
>
|
||||||
|
{translate('Cast')}
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
<Tab
|
||||||
|
className={styles.tab}
|
||||||
|
selectedClassName={styles.selectedTab}
|
||||||
|
>
|
||||||
|
{translate('Crew')}
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
{
|
||||||
|
selectedTabIndex === 1 &&
|
||||||
|
<div className={styles.filterIcon}>
|
||||||
|
<InteractiveSearchFilterMenuConnector />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
</TabList>
|
||||||
|
|
||||||
|
<TabPanel>
|
||||||
|
<MovieHistoryTable
|
||||||
|
movieId={id}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
|
||||||
|
<TabPanel>
|
||||||
|
<InteractiveSearchTable
|
||||||
|
movieId={id}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
|
||||||
|
<TabPanel>
|
||||||
|
<MovieFileEditorTable
|
||||||
|
movieId={id}
|
||||||
|
/>
|
||||||
|
<ExtraFileTable
|
||||||
|
movieId={id}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
|
||||||
|
<TabPanel>
|
||||||
|
<MovieTitlesTable
|
||||||
|
movieId={id}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
|
||||||
|
<TabPanel>
|
||||||
|
<MovieCastPostersConnector
|
||||||
|
isSmallScreen={isSmallScreen}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
|
||||||
|
<TabPanel>
|
||||||
|
<MovieCrewPostersConnector
|
||||||
|
isSmallScreen={isSmallScreen}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
<FieldSet legend={translate('Titles')}>
|
|
||||||
<MovieTitlesTable
|
|
||||||
movieId={id}
|
|
||||||
/>
|
|
||||||
</FieldSet>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<OrganizePreviewModalConnector
|
<OrganizePreviewModalConnector
|
||||||
@@ -692,12 +764,6 @@ class MovieDetails extends Component {
|
|||||||
onDeleteMoviePress={this.onDeleteMoviePress}
|
onDeleteMoviePress={this.onDeleteMoviePress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MovieHistoryModal
|
|
||||||
isOpen={isMovieHistoryModalOpen}
|
|
||||||
movieId={id}
|
|
||||||
onModalClose={this.onMovieHistoryModalClose}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DeleteMovieModal
|
<DeleteMovieModal
|
||||||
isOpen={isDeleteMovieModalOpen}
|
isOpen={isDeleteMovieModalOpen}
|
||||||
movieId={id}
|
movieId={id}
|
||||||
@@ -708,19 +774,12 @@ class MovieDetails extends Component {
|
|||||||
<InteractiveImportModal
|
<InteractiveImportModal
|
||||||
isOpen={isInteractiveImportModalOpen}
|
isOpen={isInteractiveImportModalOpen}
|
||||||
movieId={id}
|
movieId={id}
|
||||||
modalTitle={translate('ManageFiles')}
|
|
||||||
folder={path}
|
folder={path}
|
||||||
allowMovieChange={false}
|
allowMovieChange={false}
|
||||||
showFilterExistingFiles={true}
|
showFilterExistingFiles={true}
|
||||||
showImportMode={false}
|
showImportMode={false}
|
||||||
onModalClose={this.onInteractiveImportModalClose}
|
onModalClose={this.onInteractiveImportModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MovieInteractiveSearchModalConnector
|
|
||||||
isOpen={isInteractiveSearchModalOpen}
|
|
||||||
movieId={id}
|
|
||||||
onModalClose={this.onInteractiveSearchModalClose}
|
|
||||||
/>
|
|
||||||
</PageContentBody>
|
</PageContentBody>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
);
|
);
|
||||||
@@ -738,7 +797,7 @@ MovieDetails.propTypes = {
|
|||||||
certification: PropTypes.string,
|
certification: PropTypes.string,
|
||||||
ratings: PropTypes.object.isRequired,
|
ratings: PropTypes.object.isRequired,
|
||||||
path: PropTypes.string.isRequired,
|
path: PropTypes.string.isRequired,
|
||||||
statistics: PropTypes.object.isRequired,
|
sizeOnDisk: PropTypes.number.isRequired,
|
||||||
qualityProfileId: PropTypes.number.isRequired,
|
qualityProfileId: PropTypes.number.isRequired,
|
||||||
monitored: PropTypes.bool.isRequired,
|
monitored: PropTypes.bool.isRequired,
|
||||||
status: PropTypes.string.isRequired,
|
status: PropTypes.string.isRequired,
|
||||||
@@ -771,15 +830,15 @@ MovieDetails.propTypes = {
|
|||||||
onRefreshPress: PropTypes.func.isRequired,
|
onRefreshPress: PropTypes.func.isRequired,
|
||||||
onSearchPress: PropTypes.func.isRequired,
|
onSearchPress: PropTypes.func.isRequired,
|
||||||
onGoToMovie: PropTypes.func.isRequired,
|
onGoToMovie: PropTypes.func.isRequired,
|
||||||
queueItem: PropTypes.object,
|
queueItems: PropTypes.arrayOf(PropTypes.object),
|
||||||
movieRuntimeFormat: PropTypes.string.isRequired
|
movieRuntimeFormat: PropTypes.string.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
MovieDetails.defaultProps = {
|
MovieDetails.defaultProps = {
|
||||||
genres: [],
|
genres: [],
|
||||||
statistics: {},
|
|
||||||
tags: [],
|
tags: [],
|
||||||
isSaving: false
|
isSaving: false,
|
||||||
|
sizeOnDisk: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MovieDetails;
|
export default MovieDetails;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { toggleMovieMonitored } from 'Store/Actions/movieActions';
|
|||||||
import { clearMovieBlocklist, fetchMovieBlocklist } from 'Store/Actions/movieBlocklistActions';
|
import { clearMovieBlocklist, fetchMovieBlocklist } from 'Store/Actions/movieBlocklistActions';
|
||||||
import { clearMovieCredits, fetchMovieCredits } from 'Store/Actions/movieCreditsActions';
|
import { clearMovieCredits, fetchMovieCredits } from 'Store/Actions/movieCreditsActions';
|
||||||
import { clearMovieFiles, fetchMovieFiles } from 'Store/Actions/movieFileActions';
|
import { clearMovieFiles, fetchMovieFiles } from 'Store/Actions/movieFileActions';
|
||||||
|
import { clearMovieHistory, fetchMovieHistory } from 'Store/Actions/movieHistoryActions';
|
||||||
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
|
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
|
||||||
import { cancelFetchReleases, clearReleases } from 'Store/Actions/releaseActions';
|
import { cancelFetchReleases, clearReleases } from 'Store/Actions/releaseActions';
|
||||||
import { fetchImportListSchema } from 'Store/Actions/settingsActions';
|
import { fetchImportListSchema } from 'Store/Actions/settingsActions';
|
||||||
@@ -33,11 +34,14 @@ const selectMovieFiles = createSelector(
|
|||||||
|
|
||||||
const hasMovieFiles = !!items.length;
|
const hasMovieFiles = !!items.length;
|
||||||
|
|
||||||
|
const sizeOnDisk = items.map((item) => item.size).reduce((prev, curr) => prev + curr, 0);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isMovieFilesFetching: isFetching,
|
isMovieFilesFetching: isFetching,
|
||||||
isMovieFilesPopulated: isPopulated,
|
isMovieFilesPopulated: isPopulated,
|
||||||
movieFilesError: error,
|
movieFilesError: error,
|
||||||
hasMovieFiles
|
hasMovieFiles,
|
||||||
|
sizeOnDisk
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -101,7 +105,8 @@ function createMapStateToProps() {
|
|||||||
isMovieFilesFetching,
|
isMovieFilesFetching,
|
||||||
isMovieFilesPopulated,
|
isMovieFilesPopulated,
|
||||||
movieFilesError,
|
movieFilesError,
|
||||||
hasMovieFiles
|
hasMovieFiles,
|
||||||
|
sizeOnDisk
|
||||||
} = movieFiles;
|
} = movieFiles;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -140,8 +145,6 @@ function createMapStateToProps() {
|
|||||||
return acc;
|
return acc;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const queueItem = queueItems.find((item) => item.movieId === movie.id);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...movie,
|
...movie,
|
||||||
alternateTitles,
|
alternateTitles,
|
||||||
@@ -157,11 +160,12 @@ function createMapStateToProps() {
|
|||||||
movieCreditsError,
|
movieCreditsError,
|
||||||
extraFilesError,
|
extraFilesError,
|
||||||
hasMovieFiles,
|
hasMovieFiles,
|
||||||
|
sizeOnDisk,
|
||||||
previousMovie,
|
previousMovie,
|
||||||
nextMovie,
|
nextMovie,
|
||||||
isSmallScreen: dimensions.isSmallScreen,
|
isSmallScreen: dimensions.isSmallScreen,
|
||||||
isSidebarVisible,
|
isSidebarVisible,
|
||||||
queueItem,
|
queueItems,
|
||||||
movieRuntimeFormat
|
movieRuntimeFormat
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -176,6 +180,12 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
dispatchClearMovieFiles() {
|
dispatchClearMovieFiles() {
|
||||||
dispatch(clearMovieFiles());
|
dispatch(clearMovieFiles());
|
||||||
},
|
},
|
||||||
|
dispatchFetchMovieHistory({ movieId }) {
|
||||||
|
dispatch(fetchMovieHistory({ movieId }));
|
||||||
|
},
|
||||||
|
dispatchClearMovieHistory() {
|
||||||
|
dispatch(clearMovieHistory());
|
||||||
|
},
|
||||||
dispatchFetchMovieCredits({ movieId }) {
|
dispatchFetchMovieCredits({ movieId }) {
|
||||||
dispatch(fetchMovieCredits({ movieId }));
|
dispatch(fetchMovieCredits({ movieId }));
|
||||||
},
|
},
|
||||||
@@ -271,6 +281,7 @@ class MovieDetailsConnector extends Component {
|
|||||||
|
|
||||||
this.props.dispatchFetchMovieFiles({ movieId });
|
this.props.dispatchFetchMovieFiles({ movieId });
|
||||||
this.props.dispatchFetchMovieBlocklist({ movieId });
|
this.props.dispatchFetchMovieBlocklist({ movieId });
|
||||||
|
this.props.dispatchFetchMovieHistory({ movieId });
|
||||||
this.props.dispatchFetchExtraFiles({ movieId });
|
this.props.dispatchFetchExtraFiles({ movieId });
|
||||||
this.props.dispatchFetchMovieCredits({ movieId });
|
this.props.dispatchFetchMovieCredits({ movieId });
|
||||||
this.props.dispatchFetchQueueDetails({ movieId });
|
this.props.dispatchFetchQueueDetails({ movieId });
|
||||||
@@ -281,6 +292,7 @@ class MovieDetailsConnector extends Component {
|
|||||||
this.props.dispatchCancelFetchReleases();
|
this.props.dispatchCancelFetchReleases();
|
||||||
this.props.dispatchClearMovieBlocklist();
|
this.props.dispatchClearMovieBlocklist();
|
||||||
this.props.dispatchClearMovieFiles();
|
this.props.dispatchClearMovieFiles();
|
||||||
|
this.props.dispatchClearMovieHistory();
|
||||||
this.props.dispatchClearExtraFiles();
|
this.props.dispatchClearExtraFiles();
|
||||||
this.props.dispatchClearMovieCredits();
|
this.props.dispatchClearMovieCredits();
|
||||||
this.props.dispatchClearQueueDetails();
|
this.props.dispatchClearQueueDetails();
|
||||||
@@ -337,6 +349,8 @@ MovieDetailsConnector.propTypes = {
|
|||||||
isSmallScreen: PropTypes.bool.isRequired,
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
dispatchFetchMovieFiles: PropTypes.func.isRequired,
|
dispatchFetchMovieFiles: PropTypes.func.isRequired,
|
||||||
dispatchClearMovieFiles: PropTypes.func.isRequired,
|
dispatchClearMovieFiles: PropTypes.func.isRequired,
|
||||||
|
dispatchFetchMovieHistory: PropTypes.func.isRequired,
|
||||||
|
dispatchClearMovieHistory: PropTypes.func.isRequired,
|
||||||
dispatchFetchExtraFiles: PropTypes.func.isRequired,
|
dispatchFetchExtraFiles: PropTypes.func.isRequired,
|
||||||
dispatchClearExtraFiles: PropTypes.func.isRequired,
|
dispatchClearExtraFiles: PropTypes.func.isRequired,
|
||||||
dispatchFetchMovieCredits: PropTypes.func.isRequired,
|
dispatchFetchMovieCredits: PropTypes.func.isRequired,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import translate from 'Utilities/String/translate';
|
|||||||
import styles from './MovieStatusLabel.css';
|
import styles from './MovieStatusLabel.css';
|
||||||
|
|
||||||
function getMovieStatus(hasFile, isMonitored, isAvailable, queueItem = false) {
|
function getMovieStatus(hasFile, isMonitored, isAvailable, queueItem = false) {
|
||||||
|
|
||||||
if (queueItem) {
|
if (queueItem) {
|
||||||
const queueStatus = queueItem.status;
|
const queueStatus = queueItem.status;
|
||||||
const queueState = queueItem.trackedDownloadStatus;
|
const queueState = queueItem.trackedDownloadStatus;
|
||||||
@@ -115,4 +116,8 @@ MovieStatusLabel.propTypes = {
|
|||||||
colorImpairedMode: PropTypes.bool
|
colorImpairedMode: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MovieStatusLabel.defaultProps = {
|
||||||
|
title: ''
|
||||||
|
};
|
||||||
|
|
||||||
export default MovieStatusLabel;
|
export default MovieStatusLabel;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MovieTitlesTableContentConnector from './MovieTitlesTableContentConnector';
|
import MovieTitlesTableContentConnector from './MovieTitlesTableContentConnector';
|
||||||
import styles from './MovieTitlesTable.css';
|
|
||||||
|
|
||||||
function MovieTitlesTable(props) {
|
function MovieTitlesTable(props) {
|
||||||
const {
|
const {
|
||||||
@@ -8,11 +7,9 @@ function MovieTitlesTable(props) {
|
|||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<MovieTitlesTableContentConnector
|
||||||
<MovieTitlesTableContentConnector
|
{...otherProps}
|
||||||
{...otherProps}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,43 +6,31 @@ import MovieTitlesTableContent from './MovieTitlesTableContent';
|
|||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state, { movieId }) => movieId,
|
|
||||||
(state) => state.movies,
|
(state) => state.movies,
|
||||||
(movieId, movies) => {
|
(movies) => {
|
||||||
const {
|
return movies;
|
||||||
isFetching,
|
|
||||||
isPopulated,
|
|
||||||
error,
|
|
||||||
items
|
|
||||||
} = movies;
|
|
||||||
|
|
||||||
const alternateTitles = items.find((m) => m.id === movieId)?.alternateTitles;
|
|
||||||
|
|
||||||
return {
|
|
||||||
isFetching,
|
|
||||||
isPopulated,
|
|
||||||
error,
|
|
||||||
alternateTitles
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
// fetchMovies
|
||||||
|
};
|
||||||
|
|
||||||
class MovieTitlesTableContentConnector extends Component {
|
class MovieTitlesTableContentConnector extends Component {
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const movie = this.props.items.filter((obj) => {
|
||||||
alternateTitles,
|
return obj.id === this.props.movieId;
|
||||||
...otherProps
|
});
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MovieTitlesTableContent
|
<MovieTitlesTableContent
|
||||||
{...otherProps}
|
{...this.props}
|
||||||
items={alternateTitles}
|
items={movie[0].alternateTitles}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -50,11 +38,7 @@ class MovieTitlesTableContentConnector extends Component {
|
|||||||
|
|
||||||
MovieTitlesTableContentConnector.propTypes = {
|
MovieTitlesTableContentConnector.propTypes = {
|
||||||
movieId: PropTypes.number.isRequired,
|
movieId: PropTypes.number.isRequired,
|
||||||
alternateTitles: PropTypes.arrayOf(PropTypes.object).isRequired
|
items: PropTypes.arrayOf(PropTypes.object).isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
MovieTitlesTableContentConnector.defaultProps = {
|
export default connect(createMapStateToProps, mapDispatchToProps)(MovieTitlesTableContentConnector);
|
||||||
alternateTitles: []
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps)(MovieTitlesTableContentConnector);
|
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import Modal from 'Components/Modal/Modal';
|
|
||||||
import { sizes } from 'Helpers/Props';
|
|
||||||
import MovieHistoryModalContentConnector from './MovieHistoryModalContentConnector';
|
|
||||||
|
|
||||||
function MovieHistoryModal(props) {
|
|
||||||
const {
|
|
||||||
isOpen,
|
|
||||||
onModalClose,
|
|
||||||
...otherProps
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
isOpen={isOpen}
|
|
||||||
onModalClose={onModalClose}
|
|
||||||
size={sizes.EXTRA_LARGE}
|
|
||||||
>
|
|
||||||
<MovieHistoryModalContentConnector
|
|
||||||
{...otherProps}
|
|
||||||
onModalClose={onModalClose}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
MovieHistoryModal.propTypes = {
|
|
||||||
isOpen: PropTypes.bool.isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MovieHistoryModal;
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import Alert from 'Components/Alert';
|
|
||||||
import Icon from 'Components/Icon';
|
|
||||||
import Button from 'Components/Link/Button';
|
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
|
||||||
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 Table from 'Components/Table/Table';
|
|
||||||
import TableBody from 'Components/Table/TableBody';
|
|
||||||
import { icons, kinds } from 'Helpers/Props';
|
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
import MovieHistoryRowConnector from './MovieHistoryRowConnector';
|
|
||||||
|
|
||||||
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 MovieHistoryModalContent extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
isFetching,
|
|
||||||
isPopulated,
|
|
||||||
error,
|
|
||||||
items,
|
|
||||||
onMarkAsFailedPress,
|
|
||||||
onModalClose
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const hasItems = !!items.length;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ModalContent onModalClose={onModalClose}>
|
|
||||||
<ModalHeader>
|
|
||||||
{translate('History')}
|
|
||||||
</ModalHeader>
|
|
||||||
|
|
||||||
<ModalBody>
|
|
||||||
{
|
|
||||||
isFetching &&
|
|
||||||
<LoadingIndicator />
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
!isFetching && !!error &&
|
|
||||||
<Alert kind={kinds.DANGER}>{translate('HistoryLoadError')}</Alert>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
isPopulated && !hasItems && !error &&
|
|
||||||
<div>{translate('NoHistory')}</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
isPopulated && hasItems && !error &&
|
|
||||||
<Table columns={columns}>
|
|
||||||
<TableBody>
|
|
||||||
{
|
|
||||||
items.map((item) => {
|
|
||||||
return (
|
|
||||||
<MovieHistoryRowConnector
|
|
||||||
key={item.id}
|
|
||||||
{...item}
|
|
||||||
onMarkAsFailedPress={onMarkAsFailedPress}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
}
|
|
||||||
</ModalBody>
|
|
||||||
|
|
||||||
<ModalFooter>
|
|
||||||
<Button onPress={onModalClose}>
|
|
||||||
{translate('Close')}
|
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</ModalContent>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MovieHistoryModalContent.propTypes = {
|
|
||||||
isFetching: PropTypes.bool.isRequired,
|
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
|
||||||
error: PropTypes.object,
|
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
onMarkAsFailedPress: PropTypes.func.isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MovieHistoryModalContent;
|
|
||||||
@@ -7,7 +7,8 @@ import ConfirmModal from 'Components/Modal/ConfirmModal';
|
|||||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import TableRow from 'Components/Table/TableRow';
|
import TableRow from 'Components/Table/TableRow';
|
||||||
import { icons, kinds } from 'Helpers/Props';
|
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||||
|
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
||||||
import MovieFormats from 'Movie/MovieFormats';
|
import MovieFormats from 'Movie/MovieFormats';
|
||||||
import MovieLanguage from 'Movie/MovieLanguage';
|
import MovieLanguage from 'Movie/MovieLanguage';
|
||||||
import MovieQuality from 'Movie/MovieQuality';
|
import MovieQuality from 'Movie/MovieQuality';
|
||||||
@@ -102,11 +103,20 @@ class MovieHistoryRow extends Component {
|
|||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
<TableRowCell>
|
<TableRowCell>
|
||||||
<MovieFormats formats={customFormats} />
|
<MovieFormats
|
||||||
|
formats={customFormats}
|
||||||
|
/>
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
<TableRowCell className={styles.customFormatScore}>
|
<TableRowCell className={styles.customFormatScore}>
|
||||||
{formatCustomFormatScore(customFormatScore, customFormats.length)}
|
<Tooltip
|
||||||
|
anchor={formatCustomFormatScore(
|
||||||
|
customFormatScore,
|
||||||
|
customFormats.length
|
||||||
|
)}
|
||||||
|
tooltip={<MovieFormats formats={customFormats} />}
|
||||||
|
position={tooltipPositions.TOP}
|
||||||
|
/>
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
<RelativeDateCellConnector
|
<RelativeDateCellConnector
|
||||||
@@ -124,7 +134,6 @@ class MovieHistoryRow extends Component {
|
|||||||
<IconButton
|
<IconButton
|
||||||
title={translate('MarkAsFailed')}
|
title={translate('MarkAsFailed')}
|
||||||
name={icons.REMOVE}
|
name={icons.REMOVE}
|
||||||
size={14}
|
|
||||||
onPress={this.onMarkAsFailedPress}
|
onPress={this.onMarkAsFailedPress}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -1,4 +1,5 @@
|
|||||||
.container {
|
.container {
|
||||||
|
margin-top: 20px;
|
||||||
border: 1px solid var(--borderColor);
|
border: 1px solid var(--borderColor);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: var(--inputBackgroundColor);
|
background-color: var(--inputBackgroundColor);
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import MovieHistoryTableContentConnector from './MovieHistoryTableContentConnector';
|
||||||
|
import styles from './MovieHistoryTable.css';
|
||||||
|
|
||||||
|
function MovieHistoryTable(props) {
|
||||||
|
const {
|
||||||
|
...otherProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<MovieHistoryTableContentConnector
|
||||||
|
{...otherProps}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
MovieHistoryTable.propTypes = {
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MovieHistoryTable;
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import Icon from 'Components/Icon';
|
||||||
|
import IconButton from 'Components/Link/IconButton';
|
||||||
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import Table from 'Components/Table/Table';
|
||||||
|
import TableBody from 'Components/Table/TableBody';
|
||||||
|
import { icons } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import MovieHistoryRowConnector from './MovieHistoryRowConnector';
|
||||||
|
import styles from './MovieHistoryTableContent.css';
|
||||||
|
|
||||||
|
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: 'Custom format score'
|
||||||
|
}),
|
||||||
|
isSortable: true,
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'date',
|
||||||
|
label: () => translate('Date'),
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'actions',
|
||||||
|
label: React.createElement(IconButton, { name: icons.ADVANCED_SETTINGS }),
|
||||||
|
isVisible: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
class MovieHistoryTableContent extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
error,
|
||||||
|
items,
|
||||||
|
onMarkAsFailedPress
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const hasItems = !!items.length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
isFetching &&
|
||||||
|
<LoadingIndicator />
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
!isFetching && !!error &&
|
||||||
|
<div className={styles.blankpad}>
|
||||||
|
{translate('UnableToLoadHistory')}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
isPopulated && !hasItems && !error &&
|
||||||
|
<div className={styles.blankpad}>
|
||||||
|
{translate('NoHistory')}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
isPopulated && hasItems && !error &&
|
||||||
|
<Table columns={columns}>
|
||||||
|
<TableBody>
|
||||||
|
{
|
||||||
|
items.map((item) => {
|
||||||
|
return (
|
||||||
|
<MovieHistoryRowConnector
|
||||||
|
key={item.id}
|
||||||
|
{...item}
|
||||||
|
onMarkAsFailedPress={onMarkAsFailedPress}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MovieHistoryTableContent.propTypes = {
|
||||||
|
isFetching: PropTypes.bool.isRequired,
|
||||||
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
|
error: PropTypes.object,
|
||||||
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
onMarkAsFailedPress: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MovieHistoryTableContent;
|
||||||
+6
-27
@@ -2,8 +2,8 @@ import PropTypes from 'prop-types';
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { clearMovieHistory, fetchMovieHistory, movieHistoryMarkAsFailed } from 'Store/Actions/movieHistoryActions';
|
import { movieHistoryMarkAsFailed } from 'Store/Actions/movieHistoryActions';
|
||||||
import MovieHistoryModalContent from './MovieHistoryModalContent';
|
import MovieHistoryTableContent from './MovieHistoryTableContent';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
@@ -15,29 +15,10 @@ function createMapStateToProps() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
fetchMovieHistory,
|
|
||||||
clearMovieHistory,
|
|
||||||
movieHistoryMarkAsFailed
|
movieHistoryMarkAsFailed
|
||||||
};
|
};
|
||||||
|
|
||||||
class MovieHistoryModalContentConnector extends Component {
|
class MovieHistoryTableContentConnector extends Component {
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
const {
|
|
||||||
movieId
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
this.props.fetchMovieHistory({
|
|
||||||
movieId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.props.clearMovieHistory();
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
@@ -58,7 +39,7 @@ class MovieHistoryModalContentConnector extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<MovieHistoryModalContent
|
<MovieHistoryTableContent
|
||||||
{...this.props}
|
{...this.props}
|
||||||
onMarkAsFailedPress={this.onMarkAsFailedPress}
|
onMarkAsFailedPress={this.onMarkAsFailedPress}
|
||||||
/>
|
/>
|
||||||
@@ -66,11 +47,9 @@ class MovieHistoryModalContentConnector extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MovieHistoryModalContentConnector.propTypes = {
|
MovieHistoryTableContentConnector.propTypes = {
|
||||||
movieId: PropTypes.number.isRequired,
|
movieId: PropTypes.number.isRequired,
|
||||||
fetchMovieHistory: PropTypes.func.isRequired,
|
|
||||||
clearMovieHistory: PropTypes.func.isRequired,
|
|
||||||
movieHistoryMarkAsFailed: PropTypes.func.isRequired
|
movieHistoryMarkAsFailed: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps)(MovieHistoryModalContentConnector);
|
export default connect(createMapStateToProps, mapDispatchToProps)(MovieHistoryTableContentConnector);
|
||||||
@@ -17,13 +17,13 @@ function createUnoptimizedSelector() {
|
|||||||
createClientSideCollectionSelector('movies', 'movieIndex'),
|
createClientSideCollectionSelector('movies', 'movieIndex'),
|
||||||
(movies: MoviesAppState) => {
|
(movies: MoviesAppState) => {
|
||||||
return movies.items.map((m) => {
|
return movies.items.map((m) => {
|
||||||
const { monitored, status, hasFile, statistics } = m;
|
const { monitored, status, hasFile, sizeOnDisk } = m;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
monitored,
|
monitored,
|
||||||
status,
|
status,
|
||||||
hasFile,
|
hasFile,
|
||||||
statistics,
|
sizeOnDisk,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -44,20 +44,16 @@ export default function MovieIndexFooter() {
|
|||||||
let monitored = 0;
|
let monitored = 0;
|
||||||
let totalFileSize = 0;
|
let totalFileSize = 0;
|
||||||
|
|
||||||
movies.forEach((m) => {
|
movies.forEach((s) => {
|
||||||
const { statistics = { sizeOnDisk: 0 } } = m;
|
if (s.hasFile) {
|
||||||
|
|
||||||
const { sizeOnDisk = 0 } = statistics;
|
|
||||||
|
|
||||||
if (m.hasFile) {
|
|
||||||
movieFiles += 1;
|
movieFiles += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m.monitored) {
|
if (s.monitored) {
|
||||||
monitored++;
|
monitored++;
|
||||||
}
|
}
|
||||||
|
|
||||||
totalFileSize += sizeOnDisk;
|
totalFileSize += s.sizeOnDisk;
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useSelect } from 'App/SelectContext';
|
import { useSelect } from 'App/SelectContext';
|
||||||
import ClientSideCollectionAppState from 'App/State/ClientSideCollectionAppState';
|
import ClientSideCollectionAppState from 'App/State/ClientSideCollectionAppState';
|
||||||
import MoviesAppState, { MovieIndexAppState } from 'App/State/MoviesAppState';
|
import MoviesAppState, { MovieIndexAppState } from 'App/State/MoviesAppState';
|
||||||
import { MOVIE_SEARCH } from 'Commands/commandNames';
|
import { MOVIE_SEARCH } from 'Commands/commandNames';
|
||||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
|
||||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||||
import { icons, kinds } from 'Helpers/Props';
|
import { icons } from 'Helpers/Props';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||||
import createMovieClientSideCollectionItemsSelector from 'Store/Selectors/createMovieClientSideCollectionItemsSelector';
|
import createMovieClientSideCollectionItemsSelector from 'Store/Selectors/createMovieClientSideCollectionItemsSelector';
|
||||||
@@ -22,12 +21,11 @@ function MovieIndexSearchButton(props: MovieIndexSearchButtonProps) {
|
|||||||
const isSearching = useSelector(createCommandExecutingSelector(MOVIE_SEARCH));
|
const isSearching = useSelector(createCommandExecutingSelector(MOVIE_SEARCH));
|
||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
|
totalItems,
|
||||||
}: MoviesAppState & MovieIndexAppState & ClientSideCollectionAppState =
|
}: MoviesAppState & MovieIndexAppState & ClientSideCollectionAppState =
|
||||||
useSelector(createMovieClientSideCollectionItemsSelector('movieIndex'));
|
useSelector(createMovieClientSideCollectionItemsSelector('movieIndex'));
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false);
|
|
||||||
|
|
||||||
const { isSelectMode, selectedFilterKey } = props;
|
const { isSelectMode, selectedFilterKey } = props;
|
||||||
const [selectState] = useSelect();
|
const [selectState] = useSelect();
|
||||||
const { selectedState } = selectState;
|
const { selectedState } = selectState;
|
||||||
@@ -52,8 +50,6 @@ function MovieIndexSearchButton(props: MovieIndexSearchButtonProps) {
|
|||||||
: translate('SearchAll');
|
: translate('SearchAll');
|
||||||
|
|
||||||
const onPress = useCallback(() => {
|
const onPress = useCallback(() => {
|
||||||
setIsConfirmModalOpen(false);
|
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
executeCommand({
|
executeCommand({
|
||||||
name: MOVIE_SEARCH,
|
name: MOVIE_SEARCH,
|
||||||
@@ -62,36 +58,14 @@ function MovieIndexSearchButton(props: MovieIndexSearchButtonProps) {
|
|||||||
);
|
);
|
||||||
}, [dispatch, moviesToSearch]);
|
}, [dispatch, moviesToSearch]);
|
||||||
|
|
||||||
const onConfirmPress = useCallback(() => {
|
|
||||||
setIsConfirmModalOpen(true);
|
|
||||||
}, [setIsConfirmModalOpen]);
|
|
||||||
|
|
||||||
const onConfirmModalClose = useCallback(() => {
|
|
||||||
setIsConfirmModalOpen(false);
|
|
||||||
}, [setIsConfirmModalOpen]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<PageToolbarButton
|
||||||
<PageToolbarButton
|
label={isSelectMode ? searchSelectLabel : searchIndexLabel}
|
||||||
label={isSelectMode ? searchSelectLabel : searchIndexLabel}
|
isSpinning={isSearching}
|
||||||
isSpinning={isSearching}
|
isDisabled={!totalItems}
|
||||||
isDisabled={!items.length}
|
iconName={icons.SEARCH}
|
||||||
iconName={icons.SEARCH}
|
onPress={onPress}
|
||||||
onPress={moviesToSearch.length > 5 ? onConfirmPress : onPress}
|
/>
|
||||||
/>
|
|
||||||
|
|
||||||
<ConfirmModal
|
|
||||||
isOpen={isConfirmModalOpen}
|
|
||||||
kind={kinds.DANGER}
|
|
||||||
title={isSelectMode ? searchSelectLabel : searchIndexLabel}
|
|
||||||
message={translate('SearchMoviesConfirmationMessageText', {
|
|
||||||
count: moviesToSearch.length,
|
|
||||||
})}
|
|
||||||
confirmLabel={isSelectMode ? searchSelectLabel : searchIndexLabel}
|
|
||||||
onConfirm={onPress}
|
|
||||||
onCancel={onConfirmModalClose}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
|
|||||||
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
||||||
import MovieIndexProgressBar from 'Movie/Index/ProgressBar/MovieIndexProgressBar';
|
import MovieIndexProgressBar from 'Movie/Index/ProgressBar/MovieIndexProgressBar';
|
||||||
import MovieIndexPosterSelect from 'Movie/Index/Select/MovieIndexPosterSelect';
|
import MovieIndexPosterSelect from 'Movie/Index/Select/MovieIndexPosterSelect';
|
||||||
import { Statistics } from 'Movie/Movie';
|
|
||||||
import MoviePoster from 'Movie/MoviePoster';
|
import MoviePoster from 'Movie/MoviePoster';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
import dimensions from 'Styles/Variables/dimensions';
|
import dimensions from 'Styles/Variables/dimensions';
|
||||||
@@ -67,19 +66,17 @@ function MovieIndexOverview(props: MovieIndexOverviewProps) {
|
|||||||
status,
|
status,
|
||||||
path,
|
path,
|
||||||
overview,
|
overview,
|
||||||
statistics = {} as Statistics,
|
|
||||||
images,
|
images,
|
||||||
hasFile,
|
hasFile,
|
||||||
isAvailable,
|
isAvailable,
|
||||||
tmdbId,
|
tmdbId,
|
||||||
imdbId,
|
imdbId,
|
||||||
studio,
|
studio,
|
||||||
|
sizeOnDisk,
|
||||||
added,
|
added,
|
||||||
youTubeTrailerId,
|
youTubeTrailerId,
|
||||||
} = movie;
|
} = movie;
|
||||||
|
|
||||||
const { sizeOnDisk = 0 } = statistics;
|
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [isEditMovieModalOpen, setIsEditMovieModalOpen] = useState(false);
|
const [isEditMovieModalOpen, setIsEditMovieModalOpen] = useState(false);
|
||||||
const [isDeleteMovieModalOpen, setIsDeleteMovieModalOpen] = useState(false);
|
const [isDeleteMovieModalOpen, setIsDeleteMovieModalOpen] = useState(false);
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
|
|||||||
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
||||||
import MovieIndexProgressBar from 'Movie/Index/ProgressBar/MovieIndexProgressBar';
|
import MovieIndexProgressBar from 'Movie/Index/ProgressBar/MovieIndexProgressBar';
|
||||||
import MovieIndexPosterSelect from 'Movie/Index/Select/MovieIndexPosterSelect';
|
import MovieIndexPosterSelect from 'Movie/Index/Select/MovieIndexPosterSelect';
|
||||||
import { Statistics } from 'Movie/Movie';
|
|
||||||
import MoviePoster from 'Movie/MoviePoster';
|
import MoviePoster from 'Movie/MoviePoster';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||||
@@ -76,14 +75,12 @@ function MovieIndexPoster(props: MovieIndexPosterProps) {
|
|||||||
path,
|
path,
|
||||||
movieFile,
|
movieFile,
|
||||||
ratings,
|
ratings,
|
||||||
statistics = {} as Statistics,
|
sizeOnDisk,
|
||||||
certification,
|
certification,
|
||||||
originalTitle,
|
originalTitle,
|
||||||
originalLanguage,
|
originalLanguage,
|
||||||
} = movie;
|
} = movie;
|
||||||
|
|
||||||
const { sizeOnDisk = 0 } = statistics;
|
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [hasPosterError, setHasPosterError] = useState(false);
|
const [hasPosterError, setHasPosterError] = useState(false);
|
||||||
const [isEditMovieModalOpen, setIsEditMovieModalOpen] = useState(false);
|
const [isEditMovieModalOpen, setIsEditMovieModalOpen] = useState(false);
|
||||||
|
|||||||
@@ -244,15 +244,11 @@ export default function MovieIndexPosters(props: MovieIndexPostersProps) {
|
|||||||
|
|
||||||
if (isSmallScreen) {
|
if (isSmallScreen) {
|
||||||
const padding = bodyPaddingSmallScreen - 5;
|
const padding = bodyPaddingSmallScreen - 5;
|
||||||
const width = window.innerWidth - padding * 2;
|
|
||||||
const height = window.innerHeight;
|
|
||||||
|
|
||||||
if (width !== size.width || height !== size.height) {
|
setSize({
|
||||||
setSize({
|
width: window.innerWidth - padding * 2,
|
||||||
width,
|
height: window.innerHeight,
|
||||||
height,
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -260,18 +256,13 @@ export default function MovieIndexPosters(props: MovieIndexPostersProps) {
|
|||||||
if (current) {
|
if (current) {
|
||||||
const width = current.clientWidth;
|
const width = current.clientWidth;
|
||||||
const padding = bodyPadding - 5;
|
const padding = bodyPadding - 5;
|
||||||
const finalWidth = width - padding * 2;
|
|
||||||
|
|
||||||
if (Math.abs(size.width - finalWidth) < 20 || size.width === finalWidth) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setSize({
|
setSize({
|
||||||
width: finalWidth,
|
width: width - padding * 2,
|
||||||
height: window.innerHeight,
|
height: window.innerHeight,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [isSmallScreen, size, scrollerRef, bounds]);
|
}, [isSmallScreen, scrollerRef, bounds]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const currentScrollerRef = scrollerRef.current as HTMLElement;
|
const currentScrollerRef = scrollerRef.current as HTMLElement;
|
||||||
|
|||||||
@@ -38,7 +38,6 @@
|
|||||||
flex: 1 0 125px;
|
flex: 1 0 125px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.releaseGroups,
|
|
||||||
.inCinemas,
|
.inCinemas,
|
||||||
.physicalRelease,
|
.physicalRelease,
|
||||||
.digitalRelease,
|
.digitalRelease,
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ interface CssExports {
|
|||||||
'physicalRelease': string;
|
'physicalRelease': string;
|
||||||
'popularity': string;
|
'popularity': string;
|
||||||
'qualityProfileId': string;
|
'qualityProfileId': string;
|
||||||
'releaseGroups': string;
|
|
||||||
'rottenTomatoesRating': string;
|
'rottenTomatoesRating': string;
|
||||||
'runtime': string;
|
'runtime': string;
|
||||||
'sizeOnDisk': string;
|
'sizeOnDisk': string;
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal';
|
|||||||
import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
|
import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
|
||||||
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
||||||
import createMovieIndexItemSelector from 'Movie/Index/createMovieIndexItemSelector';
|
import createMovieIndexItemSelector from 'Movie/Index/createMovieIndexItemSelector';
|
||||||
import { Statistics } from 'Movie/Movie';
|
|
||||||
import MoviePopularityIndex from 'Movie/MoviePopularityIndex';
|
import MoviePopularityIndex from 'Movie/MoviePopularityIndex';
|
||||||
import MovieTitleLink from 'Movie/MovieTitleLink';
|
import MovieTitleLink from 'Movie/MovieTitleLink';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
@@ -27,7 +26,7 @@ import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
|||||||
import { SelectStateInputProps } from 'typings/props';
|
import { SelectStateInputProps } from 'typings/props';
|
||||||
import formatRuntime from 'Utilities/Date/formatRuntime';
|
import formatRuntime from 'Utilities/Date/formatRuntime';
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
import firstCharToUpper from 'Utilities/String/firstCharToUpper';
|
import titleCase from 'Utilities/String/titleCase';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import MovieIndexProgressBar from '../ProgressBar/MovieIndexProgressBar';
|
import MovieIndexProgressBar from '../ProgressBar/MovieIndexProgressBar';
|
||||||
import MovieStatusCell from './MovieStatusCell';
|
import MovieStatusCell from './MovieStatusCell';
|
||||||
@@ -61,7 +60,6 @@ function MovieIndexRow(props: MovieIndexRowProps) {
|
|||||||
originalLanguage,
|
originalLanguage,
|
||||||
originalTitle,
|
originalTitle,
|
||||||
added,
|
added,
|
||||||
statistics = {} as Statistics,
|
|
||||||
year,
|
year,
|
||||||
inCinemas,
|
inCinemas,
|
||||||
digitalRelease,
|
digitalRelease,
|
||||||
@@ -69,6 +67,7 @@ function MovieIndexRow(props: MovieIndexRowProps) {
|
|||||||
runtime,
|
runtime,
|
||||||
minimumAvailability,
|
minimumAvailability,
|
||||||
path,
|
path,
|
||||||
|
sizeOnDisk,
|
||||||
genres = [],
|
genres = [],
|
||||||
ratings,
|
ratings,
|
||||||
popularity,
|
popularity,
|
||||||
@@ -83,8 +82,6 @@ function MovieIndexRow(props: MovieIndexRowProps) {
|
|||||||
isSaving = false,
|
isSaving = false,
|
||||||
} = movie;
|
} = movie;
|
||||||
|
|
||||||
const { sizeOnDisk = 0, releaseGroups = [] } = statistics;
|
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [isEditMovieModalOpen, setIsEditMovieModalOpen] = useState(false);
|
const [isEditMovieModalOpen, setIsEditMovieModalOpen] = useState(false);
|
||||||
const [isDeleteMovieModalOpen, setIsDeleteMovieModalOpen] = useState(false);
|
const [isDeleteMovieModalOpen, setIsDeleteMovieModalOpen] = useState(false);
|
||||||
@@ -289,7 +286,7 @@ function MovieIndexRow(props: MovieIndexRowProps) {
|
|||||||
if (name === 'minimumAvailability') {
|
if (name === 'minimumAvailability') {
|
||||||
return (
|
return (
|
||||||
<VirtualTableRowCell key={name} className={styles[name]}>
|
<VirtualTableRowCell key={name} className={styles[name]}>
|
||||||
{translate(firstCharToUpper(minimumAvailability))}
|
{titleCase(minimumAvailability)}
|
||||||
</VirtualTableRowCell>
|
</VirtualTableRowCell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -383,20 +380,6 @@ function MovieIndexRow(props: MovieIndexRowProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name === 'releaseGroups') {
|
|
||||||
const joinedReleaseGroups = releaseGroups.join(', ');
|
|
||||||
const truncatedReleaseGroups =
|
|
||||||
releaseGroups.length > 3
|
|
||||||
? `${releaseGroups.slice(0, 3).join(', ')}...`
|
|
||||||
: joinedReleaseGroups;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<VirtualTableRowCell key={name} className={styles[name]}>
|
|
||||||
<span title={joinedReleaseGroups}>{truncatedReleaseGroups}</span>
|
|
||||||
</VirtualTableRowCell>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'tags') {
|
if (name === 'tags') {
|
||||||
return (
|
return (
|
||||||
<VirtualTableRowCell key={name} className={styles[name]}>
|
<VirtualTableRowCell key={name} className={styles[name]}>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user