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

|
||||
[](#backers)
|
||||
[](#sponsors)
|
||||
|
||||
@@ -9,7 +9,7 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '5.2.4'
|
||||
majorVersion: '5.1.2'
|
||||
minorVersion: $[counter('minorVersion', 2000)]
|
||||
radarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
||||
|
||||
@@ -36,7 +36,6 @@ class Blocklist extends Component {
|
||||
lastToggled: null,
|
||||
selectedState: {},
|
||||
isConfirmRemoveModalOpen: false,
|
||||
isConfirmClearModalOpen: false,
|
||||
items: props.items
|
||||
};
|
||||
}
|
||||
@@ -91,19 +90,6 @@ class Blocklist extends Component {
|
||||
this.setState({ isConfirmRemoveModalOpen: false });
|
||||
};
|
||||
|
||||
onClearBlocklistPress = () => {
|
||||
this.setState({ isConfirmClearModalOpen: true });
|
||||
};
|
||||
|
||||
onClearBlocklistConfirmed = () => {
|
||||
this.props.onClearBlocklistPress();
|
||||
this.setState({ isConfirmClearModalOpen: false });
|
||||
};
|
||||
|
||||
onConfirmClearModalClose = () => {
|
||||
this.setState({ isConfirmClearModalOpen: false });
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
@@ -117,6 +103,7 @@ class Blocklist extends Component {
|
||||
totalRecords,
|
||||
isRemoving,
|
||||
isClearingBlocklistExecuting,
|
||||
onClearBlocklistPress,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
@@ -124,8 +111,7 @@ class Blocklist extends Component {
|
||||
allSelected,
|
||||
allUnselected,
|
||||
selectedState,
|
||||
isConfirmRemoveModalOpen,
|
||||
isConfirmClearModalOpen
|
||||
isConfirmRemoveModalOpen
|
||||
} = this.state;
|
||||
|
||||
const selectedIds = this.getSelectedIds();
|
||||
@@ -145,9 +131,8 @@ class Blocklist extends Component {
|
||||
<PageToolbarButton
|
||||
label={translate('Clear')}
|
||||
iconName={icons.CLEAR}
|
||||
isDisabled={!items.length}
|
||||
isSpinning={isClearingBlocklistExecuting}
|
||||
onPress={this.onClearBlocklistPress}
|
||||
onPress={onClearBlocklistPress}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
|
||||
@@ -230,16 +215,6 @@ class Blocklist extends Component {
|
||||
onConfirm={this.onRemoveSelectedConfirmed}
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -81,9 +81,4 @@ QueueDetails.propTypes = {
|
||||
progressBar: PropTypes.node.isRequired
|
||||
};
|
||||
|
||||
QueueDetails.defaultProps = {
|
||||
trackedDownloadStatus: 'ok',
|
||||
trackedDownloadState: 'downloading'
|
||||
};
|
||||
|
||||
export default QueueDetails;
|
||||
|
||||
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import { tooltipPositions } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import QueueStatus from './QueueStatus';
|
||||
import styles from './QueueStatusCell.css';
|
||||
|
||||
@@ -40,8 +41,8 @@ QueueStatusCell.propTypes = {
|
||||
};
|
||||
|
||||
QueueStatusCell.defaultProps = {
|
||||
trackedDownloadStatus: 'ok',
|
||||
trackedDownloadState: 'downloading'
|
||||
trackedDownloadStatus: translate('Ok'),
|
||||
trackedDownloadState: translate('Downloading')
|
||||
};
|
||||
|
||||
export default QueueStatusCell;
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
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 formatTimeSpan from 'Utilities/Date/formatTimeSpan';
|
||||
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
||||
@@ -28,13 +25,11 @@ function TimeleftCell(props) {
|
||||
const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true });
|
||||
|
||||
return (
|
||||
<TableRowCell className={styles.timeleft}>
|
||||
<Tooltip
|
||||
anchor={<Icon name={icons.INFO} />}
|
||||
tooltip={translate('DelayingDownloadUntil', { date, time })}
|
||||
kind={kinds.INVERSE}
|
||||
position={tooltipPositions.TOP}
|
||||
/>
|
||||
<TableRowCell
|
||||
className={styles.timeleft}
|
||||
title={translate('DelayingDownloadUntil', { date, time })}
|
||||
>
|
||||
-
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
@@ -44,13 +39,11 @@ function TimeleftCell(props) {
|
||||
const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true });
|
||||
|
||||
return (
|
||||
<TableRowCell className={styles.timeleft}>
|
||||
<Tooltip
|
||||
anchor={<Icon name={icons.INFO} />}
|
||||
tooltip={translate('RetryingDownloadOn', { date, time })}
|
||||
kind={kinds.INVERSE}
|
||||
position={tooltipPositions.TOP}
|
||||
/>
|
||||
<TableRowCell
|
||||
className={styles.timeleft}
|
||||
title={translate('RetryingDownloadOn', { date, time })}
|
||||
>
|
||||
-
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import TextInput from 'Components/Form/TextInput';
|
||||
import Icon from 'Components/Icon';
|
||||
import Button from 'Components/Link/Button';
|
||||
@@ -131,12 +130,7 @@ class AddNewMovie extends Component {
|
||||
<div className={styles.helpText}>
|
||||
{translate('FailedLoadingSearchResults')}
|
||||
</div>
|
||||
<Alert kind={kinds.WARNING}>{getErrorMessage(error)}</Alert>
|
||||
<div>
|
||||
<Link to="https://wiki.servarr.com/radarr/troubleshooting#invalid-response-received-from-tmdb">
|
||||
{translate('WhySearchesCouldBeFailing')}
|
||||
</Link>
|
||||
</div>
|
||||
<div>{getErrorMessage(error)}</div>
|
||||
</div> : null
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,6 @@ class AddNewMovieSearchResult extends Component {
|
||||
ratings,
|
||||
folder,
|
||||
images,
|
||||
existingMovieId,
|
||||
isExistingMovie,
|
||||
isExclusionMovie,
|
||||
isSmallScreen,
|
||||
@@ -75,7 +74,8 @@ class AddNewMovieSearchResult extends Component {
|
||||
monitored,
|
||||
hasFile,
|
||||
isAvailable,
|
||||
movieFile,
|
||||
queueStatus,
|
||||
queueState,
|
||||
runtime,
|
||||
movieRuntimeFormat,
|
||||
certification
|
||||
@@ -120,13 +120,13 @@ class AddNewMovieSearchResult extends Component {
|
||||
{
|
||||
isExistingMovie &&
|
||||
<MovieIndexProgressBar
|
||||
movieId={existingMovieId}
|
||||
movieFile={movieFile}
|
||||
monitored={monitored}
|
||||
hasFile={hasFile}
|
||||
status={status}
|
||||
width={posterWidth}
|
||||
detailedProgressBar={true}
|
||||
queueStatus={queueStatus}
|
||||
queueState={queueState}
|
||||
isAvailable={isAvailable}
|
||||
/>
|
||||
}
|
||||
@@ -278,7 +278,6 @@ AddNewMovieSearchResult.propTypes = {
|
||||
ratings: PropTypes.object.isRequired,
|
||||
folder: PropTypes.string.isRequired,
|
||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
existingMovieId: PropTypes.number,
|
||||
isExistingMovie: PropTypes.bool.isRequired,
|
||||
isExclusionMovie: PropTypes.bool.isRequired,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
@@ -287,8 +286,9 @@ AddNewMovieSearchResult.propTypes = {
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
hasFile: PropTypes.bool.isRequired,
|
||||
isAvailable: PropTypes.bool.isRequired,
|
||||
movieFile: PropTypes.object,
|
||||
colorImpairedMode: PropTypes.bool,
|
||||
queueStatus: PropTypes.string,
|
||||
queueState: PropTypes.string,
|
||||
runtime: PropTypes.number.isRequired,
|
||||
movieRuntimeFormat: PropTypes.string.isRequired,
|
||||
certification: PropTypes.string
|
||||
|
||||
@@ -10,15 +10,17 @@ function createMapStateToProps() {
|
||||
createExistingMovieSelector(),
|
||||
createExclusionMovieSelector(),
|
||||
createDimensionsSelector(),
|
||||
(state) => state.queue.details.items,
|
||||
(state, { internalId }) => internalId,
|
||||
(state) => state.settings.ui.item.movieRuntimeFormat,
|
||||
(isExistingMovie, isExclusionMovie, dimensions, internalId, movieRuntimeFormat) => {
|
||||
(isExistingMovie, isExclusionMovie, dimensions, queueItems, internalId) => {
|
||||
const firstQueueItem = queueItems.find((q) => q.movieId === internalId && internalId > 0);
|
||||
|
||||
return {
|
||||
existingMovieId: internalId,
|
||||
isExistingMovie,
|
||||
isExclusionMovie,
|
||||
isSmallScreen: dimensions.isSmallScreen,
|
||||
movieRuntimeFormat
|
||||
queueStatus: firstQueueItem ? firstQueueItem.status : null,
|
||||
queueState: firstQueueItem ? firstQueueItem.trackedDownloadState : null
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -5,13 +5,12 @@ import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { addRootFolder, deleteRootFolder, fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||
import createRootFoldersSelector from 'Store/Selectors/createRootFoldersSelector';
|
||||
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
|
||||
import ImportMovieSelectFolder from './ImportMovieSelectFolder';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createRootFoldersSelector(),
|
||||
(state) => state.rootFolders,
|
||||
createSystemStatusSelector(),
|
||||
(rootFolders, systemStatus) => {
|
||||
return {
|
||||
|
||||
@@ -65,12 +65,12 @@ function AppUpdatedModalContent(props) {
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{translate('AppUpdated')}
|
||||
{translate('AppUpdated', { appName: 'Radarr' })}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<div>
|
||||
<InlineMarkdown data={translate('AppUpdatedVersion', { version })} blockClassName={styles.version} />
|
||||
<InlineMarkdown data={translate('AppUpdatedVersion', { appName: 'Radarr', version })} blockClassName={styles.version} />
|
||||
</div>
|
||||
|
||||
{
|
||||
|
||||
@@ -28,11 +28,11 @@ function ConnectionLostModal(props) {
|
||||
|
||||
<ModalBody>
|
||||
<div>
|
||||
{translate('ConnectionLostToBackend')}
|
||||
{translate('ConnectionLostToBackend', { appName: 'Radarr' })}
|
||||
</div>
|
||||
|
||||
<div className={styles.automatic}>
|
||||
{translate('ConnectionLostReconnect')}
|
||||
{translate('ConnectionLostReconnect', { appName: 'Radarr' })}
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
|
||||
@@ -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, {
|
||||
AppSectionFilterState,
|
||||
AppSectionItemState,
|
||||
Error,
|
||||
} 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> {
|
||||
params: unknown;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import * as commandNames from 'Commands/commandNames';
|
||||
import withScrollPosition from 'Components/withScrollPosition';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import { saveMovieCollections, setMovieCollectionsFilter, setMovieCollectionsSort } from 'Store/Actions/movieCollectionActions';
|
||||
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
|
||||
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||
import scrollPositions from 'Store/scrollPositions';
|
||||
import createCollectionClientSideCollectionItemsSelector from 'Store/Selectors/createCollectionClientSideCollectionItemsSelector';
|
||||
@@ -39,12 +38,6 @@ function createMapDispatchToProps(dispatch, props) {
|
||||
dispatchFetchRootFolders() {
|
||||
dispatch(fetchRootFolders());
|
||||
},
|
||||
dispatchFetchQueueDetails() {
|
||||
dispatch(fetchQueueDetails());
|
||||
},
|
||||
dispatchClearQueueDetails() {
|
||||
dispatch(clearQueueDetails());
|
||||
},
|
||||
onUpdateSelectedPress(payload) {
|
||||
dispatch(saveMovieCollections(payload));
|
||||
},
|
||||
@@ -70,12 +63,10 @@ class CollectionConnector extends Component {
|
||||
componentDidMount() {
|
||||
registerPagePopulator(this.repopulate);
|
||||
this.props.dispatchFetchRootFolders();
|
||||
this.props.dispatchFetchQueueDetails();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
unregisterPagePopulator(this.repopulate);
|
||||
this.props.dispatchClearQueueDetails();
|
||||
}
|
||||
|
||||
//
|
||||
@@ -108,9 +99,7 @@ CollectionConnector.propTypes = {
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
view: PropTypes.string.isRequired,
|
||||
onUpdateSelectedPress: PropTypes.func.isRequired,
|
||||
dispatchFetchRootFolders: PropTypes.func.isRequired,
|
||||
dispatchFetchQueueDetails: PropTypes.func.isRequired,
|
||||
dispatchClearQueueDetails: PropTypes.func.isRequired
|
||||
dispatchFetchRootFolders: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default withScrollPosition(
|
||||
|
||||
@@ -70,7 +70,6 @@ class CollectionMovie extends Component {
|
||||
hasFile,
|
||||
folder,
|
||||
isAvailable,
|
||||
movieFile,
|
||||
isExistingMovie,
|
||||
posterWidth,
|
||||
posterHeight,
|
||||
@@ -132,8 +131,6 @@ class CollectionMovie extends Component {
|
||||
id ?
|
||||
<div className={styles.overlayStatus}>
|
||||
<MovieIndexProgressBar
|
||||
movieId={id}
|
||||
movieFile={movieFile}
|
||||
monitored={monitored}
|
||||
hasFile={hasFile}
|
||||
status={status}
|
||||
@@ -183,7 +180,6 @@ CollectionMovie.propTypes = {
|
||||
hasFile: PropTypes.bool,
|
||||
folder: PropTypes.string,
|
||||
isAvailable: PropTypes.bool,
|
||||
movieFile: PropTypes.object,
|
||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
posterWidth: PropTypes.number.isRequired,
|
||||
posterHeight: PropTypes.number.isRequired,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
.description {
|
||||
line-height: $lineHeight;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-left: 0;
|
||||
line-height: $lineHeight;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
|
||||
@@ -23,19 +23,19 @@ const EVENT_TYPE_OPTIONS = [
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
id: 5,
|
||||
get name() {
|
||||
return translate('Deleted');
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
id: 6,
|
||||
get name() {
|
||||
return translate('Renamed');
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
id: 7,
|
||||
get name() {
|
||||
return translate('Ignored');
|
||||
},
|
||||
|
||||
@@ -3,7 +3,6 @@ import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { addRootFolder } from 'Store/Actions/rootFolderActions';
|
||||
import createRootFoldersSelector from 'Store/Selectors/createRootFoldersSelector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import RootFolderSelectInput from './RootFolderSelectInput';
|
||||
|
||||
@@ -11,7 +10,7 @@ const ADD_NEW_KEY = 'addNew';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createRootFoldersSelector(),
|
||||
(state) => state.rootFolders,
|
||||
(state, { value }) => value,
|
||||
(state, { includeMissingValue }) => includeMissingValue,
|
||||
(state, { includeNoChange }) => includeNoChange,
|
||||
|
||||
@@ -63,12 +63,6 @@
|
||||
width: 1280px;
|
||||
}
|
||||
|
||||
.extraExtraLarge {
|
||||
composes: modal;
|
||||
|
||||
width: 1600px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointExtraLarge) {
|
||||
.modal.extraLarge {
|
||||
width: 90%;
|
||||
@@ -96,8 +90,7 @@
|
||||
.modal.small,
|
||||
.modal.medium,
|
||||
.modal.large,
|
||||
.modal.extraLarge,
|
||||
.modal.extraExtraLarge {
|
||||
.modal.extraLarge {
|
||||
max-height: 100%;
|
||||
width: 100%;
|
||||
height: 100% !important;
|
||||
|
||||
1
frontend/src/Components/Modal/Modal.css.d.ts
vendored
1
frontend/src/Components/Modal/Modal.css.d.ts
vendored
@@ -1,7 +1,6 @@
|
||||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'extraExtraLarge': string;
|
||||
'extraLarge': string;
|
||||
'large': string;
|
||||
'medium': string;
|
||||
|
||||
@@ -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() {
|
||||
dispatch(executeCommand({
|
||||
name: commandNames.IMPORT_LIST_SYNC,
|
||||
commandFinished: this.dispatchFetchListMovies
|
||||
name: commandNames.IMPORT_LIST_SYNC
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -50,7 +50,7 @@ $hoverScale: 1.05;
|
||||
.title {
|
||||
@add-mixin truncate;
|
||||
|
||||
background-color: var(--movieBackgroundColor);
|
||||
background-color: #fafbfc;
|
||||
text-align: center;
|
||||
font-size: $smallFontSize;
|
||||
}
|
||||
@@ -68,19 +68,6 @@ $hoverScale: 1.05;
|
||||
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 {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
|
||||
@@ -7,7 +7,6 @@ interface CssExports {
|
||||
'controls': string;
|
||||
'editorSelect': string;
|
||||
'excluded': string;
|
||||
'existing': string;
|
||||
'externalLinks': string;
|
||||
'link': string;
|
||||
'overlayTitle': string;
|
||||
|
||||
@@ -92,7 +92,6 @@ class DiscoverMoviePoster extends Component {
|
||||
showRelativeDates,
|
||||
shortDateFormat,
|
||||
timeFormat,
|
||||
movieRuntimeFormat,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
@@ -111,7 +110,7 @@ class DiscoverMoviePoster extends Component {
|
||||
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
<div className={styles.posterContainer} title={title}>
|
||||
<div className={styles.posterContainer}>
|
||||
{
|
||||
<div className={styles.editorSelect}>
|
||||
<CheckInput
|
||||
@@ -159,14 +158,6 @@ class DiscoverMoviePoster extends Component {
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
isExisting &&
|
||||
<div
|
||||
className={styles.existing}
|
||||
title={translate('Existing')}
|
||||
/>
|
||||
}
|
||||
|
||||
<Link
|
||||
className={styles.link}
|
||||
style={elementStyle}
|
||||
@@ -194,7 +185,7 @@ class DiscoverMoviePoster extends Component {
|
||||
|
||||
{
|
||||
showTitle &&
|
||||
<div className={styles.title} title={title}>
|
||||
<div className={styles.title}>
|
||||
{title}
|
||||
</div>
|
||||
}
|
||||
@@ -203,7 +194,6 @@ class DiscoverMoviePoster extends Component {
|
||||
showRelativeDates={showRelativeDates}
|
||||
shortDateFormat={shortDateFormat}
|
||||
timeFormat={timeFormat}
|
||||
movieRuntimeFormat={movieRuntimeFormat}
|
||||
{...otherProps}
|
||||
/>
|
||||
|
||||
@@ -246,7 +236,6 @@ DiscoverMoviePoster.propTypes = {
|
||||
showRelativeDates: PropTypes.bool.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
movieRuntimeFormat: PropTypes.string.isRequired,
|
||||
isExisting: PropTypes.bool.isRequired,
|
||||
isExcluded: PropTypes.bool.isRequired,
|
||||
isSelected: PropTypes.bool,
|
||||
|
||||
@@ -5,11 +5,9 @@ import DiscoverMoviePoster from './DiscoverMoviePoster';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.settings.ui.item.movieRuntimeFormat,
|
||||
createDimensionsSelector(),
|
||||
(movieRuntimeFormat, dimensions) => {
|
||||
( dimensions) => {
|
||||
return {
|
||||
movieRuntimeFormat,
|
||||
isSmallScreen: dimensions.isSmallScreen
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.info {
|
||||
background-color: var(--movieBackgroundColor);
|
||||
background-color: #fafbfc;
|
||||
text-align: center;
|
||||
font-size: $smallFontSize;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
import TmdbRating from 'Components/TmdbRating';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import { getMovieStatusDetails } from 'Movie/MovieStatus';
|
||||
import formatRuntime from 'Utilities/Date/formatRuntime';
|
||||
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './DiscoverMoviePosterInfo.css';
|
||||
|
||||
function DiscoverMoviePosterInfo(props) {
|
||||
@@ -22,13 +19,12 @@ function DiscoverMoviePosterInfo(props) {
|
||||
sortKey,
|
||||
showRelativeDates,
|
||||
shortDateFormat,
|
||||
timeFormat,
|
||||
movieRuntimeFormat
|
||||
timeFormat
|
||||
} = props;
|
||||
|
||||
if (sortKey === 'status' && status) {
|
||||
return (
|
||||
<div className={styles.info} title={translate('Status')}>
|
||||
<div className={styles.info}>
|
||||
{getMovieStatusDetails(status).title}
|
||||
</div>
|
||||
);
|
||||
@@ -36,7 +32,7 @@ function DiscoverMoviePosterInfo(props) {
|
||||
|
||||
if (sortKey === 'studio' && studio) {
|
||||
return (
|
||||
<div className={styles.info} title={translate('Studio')}>
|
||||
<div className={styles.info}>
|
||||
{studio}
|
||||
</div>
|
||||
);
|
||||
@@ -54,8 +50,8 @@ function DiscoverMoviePosterInfo(props) {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.info} title={translate('InCinemas')}>
|
||||
<Icon name={icons.IN_CINEMAS} /> {inCinemasDate}
|
||||
<div className={styles.info}>
|
||||
{`In Cinemas ${inCinemasDate}`}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -72,8 +68,8 @@ function DiscoverMoviePosterInfo(props) {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.info} title={translate('DigitalRelease')}>
|
||||
<Icon name={icons.MOVIE_FILE} /> {digitalReleaseDate}
|
||||
<div className={styles.info}>
|
||||
{`Digital ${digitalReleaseDate}`}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -90,15 +86,15 @@ function DiscoverMoviePosterInfo(props) {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.info} title={translate('PhysicalRelease')}>
|
||||
<Icon name={icons.DISC} /> {physicalReleaseDate}
|
||||
<div className={styles.info}>
|
||||
{`Released ${physicalReleaseDate}`}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (sortKey === 'certification' && certification) {
|
||||
return (
|
||||
<div className={styles.info} title={translate('Certification')}>
|
||||
<div className={styles.info}>
|
||||
{certification}
|
||||
</div>
|
||||
);
|
||||
@@ -106,8 +102,8 @@ function DiscoverMoviePosterInfo(props) {
|
||||
|
||||
if (sortKey === 'runtime' && runtime) {
|
||||
return (
|
||||
<div className={styles.info} title={translate('Runtime')}>
|
||||
{formatRuntime(runtime, movieRuntimeFormat)}
|
||||
<div className={styles.info}>
|
||||
{formatRuntime(runtime)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -115,7 +111,9 @@ function DiscoverMoviePosterInfo(props) {
|
||||
if (sortKey === 'ratings' && ratings) {
|
||||
return (
|
||||
<div className={styles.info}>
|
||||
<TmdbRating ratings={ratings} />
|
||||
<TmdbRating
|
||||
ratings={ratings}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -135,8 +133,7 @@ DiscoverMoviePosterInfo.propTypes = {
|
||||
sortKey: PropTypes.string.isRequired,
|
||||
showRelativeDates: PropTypes.bool.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
movieRuntimeFormat: PropTypes.string.isRequired
|
||||
timeFormat: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default DiscoverMoviePosterInfo;
|
||||
|
||||
@@ -76,7 +76,6 @@ class DiscoverMovieRow extends Component {
|
||||
ratings,
|
||||
popularity,
|
||||
certification,
|
||||
movieRuntimeFormat,
|
||||
collection,
|
||||
columns,
|
||||
isExisting,
|
||||
@@ -231,7 +230,7 @@ class DiscoverMovieRow extends Component {
|
||||
key={name}
|
||||
className={styles[name]}
|
||||
>
|
||||
{formatRuntime(runtime, movieRuntimeFormat)}
|
||||
{formatRuntime(runtime)}
|
||||
</VirtualTableRowCell>
|
||||
);
|
||||
}
|
||||
@@ -398,7 +397,6 @@ DiscoverMovieRow.propTypes = {
|
||||
popularity: PropTypes.number.isRequired,
|
||||
certification: PropTypes.string,
|
||||
collection: PropTypes.object,
|
||||
movieRuntimeFormat: PropTypes.string.isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
isExisting: PropTypes.bool.isRequired,
|
||||
isExcluded: PropTypes.bool.isRequired,
|
||||
|
||||
@@ -5,11 +5,9 @@ import DiscoverMovieRow from './DiscoverMovieRow';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.settings.ui.item.movieRuntimeFormat,
|
||||
createDimensionsSelector(),
|
||||
(movieRuntimeFormat, dimensions) => {
|
||||
(dimensions) => {
|
||||
return {
|
||||
movieRuntimeFormat,
|
||||
isSmallScreen: dimensions.isSmallScreen
|
||||
};
|
||||
}
|
||||
|
||||
@@ -34,8 +34,7 @@ function AuthenticationRequiredModalContent(props) {
|
||||
authenticationMethod,
|
||||
authenticationRequired,
|
||||
username,
|
||||
password,
|
||||
passwordConfirmation
|
||||
password
|
||||
} = settings;
|
||||
|
||||
const authenticationEnabled = authenticationMethod && authenticationMethod.value !== 'none';
|
||||
@@ -64,7 +63,7 @@ function AuthenticationRequiredModalContent(props) {
|
||||
className={styles.authRequiredAlert}
|
||||
kind={kinds.WARNING}
|
||||
>
|
||||
{translate('AuthenticationRequiredWarning')}
|
||||
{translate('AuthenticationRequiredWarning', { appName: 'Radarr' })}
|
||||
</Alert>
|
||||
|
||||
{
|
||||
@@ -77,7 +76,7 @@ function AuthenticationRequiredModalContent(props) {
|
||||
type={inputTypes.SELECT}
|
||||
name="authenticationMethod"
|
||||
values={authenticationMethodOptions}
|
||||
helpText={translate('AuthenticationMethodHelpText')}
|
||||
helpText={translate('AuthenticationMethodHelpText', { appName: 'Radarr' })}
|
||||
helpTextWarning={authenticationMethod.value === 'none' ? translate('AuthenticationMethodHelpTextWarning') : undefined}
|
||||
helpLink="https://wiki.servarr.com/radarr/faq#forced-authentication"
|
||||
onChange={onInputChange}
|
||||
@@ -121,18 +120,6 @@ function AuthenticationRequiredModalContent(props) {
|
||||
{...password}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('PasswordConfirmation')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.PASSWORD}
|
||||
name="passwordConfirmation"
|
||||
onChange={onInputChange}
|
||||
helpTextWarning={passwordConfirmation?.value ? undefined : translate('AuthenticationRequiredPasswordConfirmationHelpTextWarning')}
|
||||
{...passwordConfirmation}
|
||||
/>
|
||||
</FormGroup>
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
|
||||
@@ -3,6 +3,5 @@ export const SMALL = 'small';
|
||||
export const MEDIUM = 'medium';
|
||||
export const LARGE = 'large';
|
||||
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];
|
||||
|
||||
@@ -3,16 +3,13 @@ import React, { Fragment } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import Icon from 'Components/Icon';
|
||||
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 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 translate from 'Utilities/String/translate';
|
||||
import InteractiveSearchFilterModalConnector from './InteractiveSearchFilterModalConnector';
|
||||
import InteractiveSearchRowConnector from './InteractiveSearchRowConnector';
|
||||
import styles from './InteractiveSearch.css';
|
||||
import styles from './InteractiveSearchContent.css';
|
||||
|
||||
const columns = [
|
||||
{
|
||||
@@ -27,6 +24,23 @@ const columns = [
|
||||
isSortable: 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',
|
||||
label: () => translate('Title'),
|
||||
@@ -70,6 +84,12 @@ const columns = [
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'customFormat',
|
||||
label: () => translate('Formats'),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'customFormatScore',
|
||||
label: React.createElement(Icon, {
|
||||
@@ -87,27 +107,10 @@ const columns = [
|
||||
}),
|
||||
isSortable: 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 {
|
||||
searchPayload,
|
||||
isFetching,
|
||||
@@ -115,36 +118,18 @@ function InteractiveSearch(props) {
|
||||
error,
|
||||
totalReleasesCount,
|
||||
items,
|
||||
selectedFilterKey,
|
||||
filters,
|
||||
customFilters,
|
||||
sortKey,
|
||||
sortDirection,
|
||||
longDateFormat,
|
||||
timeFormat,
|
||||
onSortPress,
|
||||
onFilterSelect,
|
||||
onGrabPress
|
||||
} = props;
|
||||
|
||||
const errorMessage = getErrorMessage(error);
|
||||
const type = 'movies';
|
||||
|
||||
return (
|
||||
<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
|
||||
}
|
||||
@@ -218,23 +203,19 @@ function InteractiveSearch(props) {
|
||||
);
|
||||
}
|
||||
|
||||
InteractiveSearch.propTypes = {
|
||||
InteractiveSearchContent.propTypes = {
|
||||
searchPayload: PropTypes.object.isRequired,
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
totalReleasesCount: PropTypes.number.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,
|
||||
sortDirection: PropTypes.string,
|
||||
longDateFormat: PropTypes.string.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
onSortPress: PropTypes.func.isRequired,
|
||||
onFilterSelect: PropTypes.func.isRequired,
|
||||
onGrabPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default InteractiveSearch;
|
||||
export default InteractiveSearchContent;
|
||||
@@ -2,11 +2,10 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { clearMovieHistory, fetchMovieHistory } from 'Store/Actions/movieHistoryActions';
|
||||
import * as releaseActions from 'Store/Actions/releaseActions';
|
||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
import InteractiveSearch from './InteractiveSearch';
|
||||
import InteractiveSearchContent from './InteractiveSearchContent';
|
||||
|
||||
function createMapStateToProps(appState) {
|
||||
return createSelector(
|
||||
@@ -30,12 +29,8 @@ function createMapDispatchToProps(dispatch, props) {
|
||||
dispatch(releaseActions.fetchReleases(payload));
|
||||
},
|
||||
|
||||
dispatchFetchMovieHistory({ movieId }) {
|
||||
dispatch(fetchMovieHistory({ movieId }));
|
||||
},
|
||||
|
||||
dispatchClearMovieHistory() {
|
||||
dispatch(clearMovieHistory());
|
||||
dispatchClearReleases(payload) {
|
||||
dispatch(releaseActions.clearReleases(payload));
|
||||
},
|
||||
|
||||
onSortPress(sortKey, sortDirection) {
|
||||
@@ -43,7 +38,8 @@ function createMapDispatchToProps(dispatch, props) {
|
||||
},
|
||||
|
||||
onFilterSelect(selectedFilterKey) {
|
||||
dispatch(releaseActions.setReleasesFilter({ selectedFilterKey }));
|
||||
const action = releaseActions.setReleasesFilter;
|
||||
dispatch(action({ selectedFilterKey }));
|
||||
},
|
||||
|
||||
onGrabPress(payload) {
|
||||
@@ -52,7 +48,7 @@ function createMapDispatchToProps(dispatch, props) {
|
||||
};
|
||||
}
|
||||
|
||||
class InteractiveSearchConnector extends Component {
|
||||
class InteractiveSearchContentConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
@@ -61,8 +57,7 @@ class InteractiveSearchConnector extends Component {
|
||||
const {
|
||||
searchPayload,
|
||||
isPopulated,
|
||||
dispatchFetchReleases,
|
||||
dispatchFetchMovieHistory
|
||||
dispatchFetchReleases
|
||||
} = this.props;
|
||||
|
||||
// If search results are not yet isPopulated fetch them,
|
||||
@@ -70,12 +65,6 @@ class InteractiveSearchConnector extends Component {
|
||||
if (!isPopulated) {
|
||||
dispatchFetchReleases(searchPayload);
|
||||
}
|
||||
|
||||
dispatchFetchMovieHistory(searchPayload);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.dispatchClearMovieHistory();
|
||||
}
|
||||
|
||||
//
|
||||
@@ -84,26 +73,24 @@ class InteractiveSearchConnector extends Component {
|
||||
render() {
|
||||
const {
|
||||
dispatchFetchReleases,
|
||||
dispatchFetchMovieHistory,
|
||||
dispatchClearMovieHistory,
|
||||
dispatchClearReleases,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
|
||||
<InteractiveSearch
|
||||
<InteractiveSearchContent
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
InteractiveSearchConnector.propTypes = {
|
||||
InteractiveSearchContentConnector.propTypes = {
|
||||
searchPayload: PropTypes.object.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
dispatchFetchReleases: PropTypes.func.isRequired,
|
||||
dispatchFetchMovieHistory: PropTypes.func.isRequired,
|
||||
dispatchClearMovieHistory: PropTypes.func.isRequired
|
||||
dispatchClearReleases: 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 { align } from 'Helpers/Props';
|
||||
import InteractiveSearchFilterModalConnector from './InteractiveSearchFilterModalConnector';
|
||||
import styles from './InteractiveSearch.css';
|
||||
import styles from './InteractiveSearchContent.css';
|
||||
|
||||
function InteractiveSearchFilterMenu(props) {
|
||||
const {
|
||||
|
||||
@@ -1,25 +1,23 @@
|
||||
.protocol {
|
||||
.cell {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
}
|
||||
|
||||
.protocol {
|
||||
composes: cell;
|
||||
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.titleContent {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.indexer {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
composes: cell;
|
||||
|
||||
width: 85px;
|
||||
}
|
||||
|
||||
.quality,
|
||||
.customFormat,
|
||||
.languages {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
composes: cell;
|
||||
}
|
||||
|
||||
.languages {
|
||||
@@ -27,7 +25,7 @@
|
||||
}
|
||||
|
||||
.customFormatScore {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
composes: cell;
|
||||
|
||||
width: 55px;
|
||||
font-weight: bold;
|
||||
@@ -35,28 +33,31 @@
|
||||
}
|
||||
|
||||
.rejected,
|
||||
.indexerFlags,
|
||||
.download {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
.indexerFlags {
|
||||
composes: cell;
|
||||
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.age,
|
||||
.size {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
composes: cell;
|
||||
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.peers {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
composes: cell;
|
||||
|
||||
width: 75px;
|
||||
}
|
||||
|
||||
.titleContent {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.history {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
composes: cell;
|
||||
|
||||
width: 75px;
|
||||
}
|
||||
@@ -66,7 +67,7 @@
|
||||
}
|
||||
|
||||
.download {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
composes: cell;
|
||||
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
interface CssExports {
|
||||
'age': string;
|
||||
'blocklist': string;
|
||||
'cell': string;
|
||||
'customFormat': string;
|
||||
'customFormatScore': string;
|
||||
'download': string;
|
||||
'downloadIcon': string;
|
||||
|
||||
@@ -133,9 +133,9 @@ function InteractiveSearchRow(props: InteractiveSearchRowProps) {
|
||||
longDateFormat,
|
||||
timeFormat,
|
||||
grabError,
|
||||
historyGrabbedData = {} as MovieHistory,
|
||||
historyFailedData = {} as MovieHistory,
|
||||
blocklistData = {} as MovieBlocklist,
|
||||
historyGrabbedData,
|
||||
historyFailedData,
|
||||
blocklistData,
|
||||
searchPayload,
|
||||
onGrabPress,
|
||||
} = props;
|
||||
@@ -199,6 +199,53 @@ function InteractiveSearchRow(props: InteractiveSearchRowProps) {
|
||||
{formatAge(age, ageHours, ageMinutes)}
|
||||
</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>
|
||||
<div className={styles.titleContent}>
|
||||
<Link to={infoUrl} title={title}>
|
||||
@@ -269,6 +316,10 @@ function InteractiveSearchRow(props: InteractiveSearchRowProps) {
|
||||
<MovieQuality quality={quality} />
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.customFormat}>
|
||||
<MovieFormats formats={customFormats} />
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.customFormatScore}>
|
||||
<Tooltip
|
||||
anchor={formatCustomFormatScore(
|
||||
@@ -297,53 +348,6 @@ function InteractiveSearchRow(props: InteractiveSearchRowProps) {
|
||||
) : null}
|
||||
</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
|
||||
isOpen={isConfirmGrabModalOpen}
|
||||
kind={kinds.WARNING}
|
||||
|
||||
16
frontend/src/InteractiveSearch/InteractiveSearchTable.js
Normal file
16
frontend/src/InteractiveSearch/InteractiveSearchTable.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import InteractiveSearchContentConnector from './InteractiveSearchContentConnector';
|
||||
|
||||
function InteractiveSearchTable(props) {
|
||||
|
||||
return (
|
||||
<InteractiveSearchContentConnector
|
||||
searchPayload={props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
InteractiveSearchTable.propTypes = {
|
||||
};
|
||||
|
||||
export default InteractiveSearchTable;
|
||||
@@ -1,8 +1,11 @@
|
||||
import PropTypes from 'prop-types';
|
||||
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 EditImportListModalConnector from 'Settings/ImportLists/ImportLists/EditImportListModalConnector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from '../MovieCreditPoster.css';
|
||||
|
||||
class MovieCastPoster extends Component {
|
||||
@@ -57,7 +60,7 @@ class MovieCastPoster extends Component {
|
||||
images,
|
||||
posterWidth,
|
||||
posterHeight,
|
||||
importList
|
||||
importListId
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
@@ -66,31 +69,36 @@ class MovieCastPoster extends Component {
|
||||
|
||||
const elementStyle = {
|
||||
width: `${posterWidth}px`,
|
||||
height: `${posterHeight}px`,
|
||||
borderRadius: '5px'
|
||||
height: `${posterHeight}px`
|
||||
};
|
||||
|
||||
const contentStyle = {
|
||||
width: `${posterWidth}px`
|
||||
};
|
||||
|
||||
const monitored = importList !== undefined && importList.enabled && importList.enableAuto;
|
||||
const importListId = importList ? importList.id : 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.content}
|
||||
style={contentStyle}
|
||||
>
|
||||
<div className={styles.posterContainer}>
|
||||
<div className={styles.controls}>
|
||||
<MonitorToggleButton
|
||||
className={styles.action}
|
||||
monitored={monitored}
|
||||
size={20}
|
||||
onPress={importListId > 0 ? this.onEditImportListPress : this.onAddImportListPress}
|
||||
/>
|
||||
</div>
|
||||
<Label className={styles.controls}>
|
||||
{
|
||||
importListId > 0 ?
|
||||
<IconButton
|
||||
className={styles.action}
|
||||
name={icons.EDIT}
|
||||
title={translate('EditPerson')}
|
||||
onPress={this.onEditImportListPress}
|
||||
/> :
|
||||
<IconButton
|
||||
className={styles.action}
|
||||
name={icons.ADD}
|
||||
title={translate('FollowPerson')}
|
||||
onPress={this.onAddImportListPress}
|
||||
/>
|
||||
}
|
||||
</Label>
|
||||
|
||||
<div
|
||||
style={elementStyle}
|
||||
@@ -140,8 +148,12 @@ MovieCastPoster.propTypes = {
|
||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
posterWidth: PropTypes.number.isRequired,
|
||||
posterHeight: PropTypes.number.isRequired,
|
||||
importList: PropTypes.object,
|
||||
importListId: PropTypes.number.isRequired,
|
||||
onImportListSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
MovieCastPoster.defaultProps = {
|
||||
importListId: 0
|
||||
};
|
||||
|
||||
export default MovieCastPoster;
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import PropTypes from 'prop-types';
|
||||
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 EditImportListModalConnector from 'Settings/ImportLists/ImportLists/EditImportListModalConnector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from '../MovieCreditPoster.css';
|
||||
|
||||
class MovieCrewPoster extends Component {
|
||||
@@ -57,7 +60,7 @@ class MovieCrewPoster extends Component {
|
||||
images,
|
||||
posterWidth,
|
||||
posterHeight,
|
||||
importList
|
||||
importListId
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
@@ -66,31 +69,36 @@ class MovieCrewPoster extends Component {
|
||||
|
||||
const elementStyle = {
|
||||
width: `${posterWidth}px`,
|
||||
height: `${posterHeight}px`,
|
||||
borderRadius: '5px'
|
||||
height: `${posterHeight}px`
|
||||
};
|
||||
|
||||
const contentStyle = {
|
||||
width: `${posterWidth}px`
|
||||
};
|
||||
|
||||
const monitored = importList !== undefined && importList.enabled && importList.enableAuto;
|
||||
const importListId = importList ? importList.id : 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.content}
|
||||
style={contentStyle}
|
||||
>
|
||||
<div className={styles.posterContainer}>
|
||||
<div className={styles.controls}>
|
||||
<MonitorToggleButton
|
||||
className={styles.action}
|
||||
monitored={monitored}
|
||||
size={20}
|
||||
onPress={importListId > 0 ? this.onEditImportListPress : this.onAddImportListPress}
|
||||
/>
|
||||
</div>
|
||||
<Label className={styles.controls}>
|
||||
{
|
||||
importListId > 0 ?
|
||||
<IconButton
|
||||
className={styles.action}
|
||||
name={icons.EDIT}
|
||||
title={translate('EditPerson')}
|
||||
onPress={this.onEditImportListPress}
|
||||
/> :
|
||||
<IconButton
|
||||
className={styles.action}
|
||||
name={icons.ADD}
|
||||
title={translate('FollowPerson')}
|
||||
onPress={this.onAddImportListPress}
|
||||
/>
|
||||
}
|
||||
</Label>
|
||||
|
||||
<div
|
||||
style={elementStyle}
|
||||
@@ -140,8 +148,12 @@ MovieCrewPoster.propTypes = {
|
||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
posterWidth: PropTypes.number.isRequired,
|
||||
posterHeight: PropTypes.number.isRequired,
|
||||
importList: PropTypes.object,
|
||||
importListId: PropTypes.number.isRequired,
|
||||
onImportListSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
MovieCrewPoster.defaultProps = {
|
||||
importListId: 0
|
||||
};
|
||||
|
||||
export default MovieCrewPoster;
|
||||
|
||||
@@ -18,7 +18,7 @@ function createMapStateToProps() {
|
||||
}, []);
|
||||
|
||||
return {
|
||||
items: _.uniqBy(crew, 'personName')
|
||||
items: crew
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
$hoverScale: 1.05;
|
||||
|
||||
.content {
|
||||
border-radius: '5px';
|
||||
transition: all 200ms ease-in;
|
||||
|
||||
&:hover {
|
||||
z-index: 2;
|
||||
box-shadow: 0 0 12px var(--black);
|
||||
transition: all 200ms ease-in;
|
||||
|
||||
.controls {
|
||||
opacity: 0.9;
|
||||
transition: opacity 200ms linear 150ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,18 +50,22 @@ $hoverScale: 1.05;
|
||||
|
||||
.controls {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
z-index: 3;
|
||||
border-radius: 4px;
|
||||
background-color: #707070;
|
||||
color: var(--white);
|
||||
font-size: $smallFontSize;
|
||||
opacity: 0;
|
||||
transition: opacity 0;
|
||||
}
|
||||
|
||||
.action {
|
||||
composes: toggleButton from '~Components/MonitorToggleButton.css';
|
||||
|
||||
width: 25px;
|
||||
color: var(--white);
|
||||
composes: button from '~Components/Link/IconButton.css';
|
||||
|
||||
&:hover {
|
||||
color: var(--iconButtonHoverLightColor);
|
||||
color: var(--radarrYellow);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,11 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { selectImportListSchema, setImportListFieldValue, setImportListValue } from 'Store/Actions/settingsActions';
|
||||
import createMovieCreditListSelector from 'Store/Selectors/createMovieCreditListSelector';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createMovieCreditListSelector(),
|
||||
(importList) => {
|
||||
return {
|
||||
importList
|
||||
};
|
||||
}
|
||||
);
|
||||
return createMovieCreditListSelector();
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
@@ -28,7 +20,7 @@ class MovieCreditPosterConnector extends Component {
|
||||
// Listeners
|
||||
|
||||
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.setImportListValue({ name: 'name', value: `${this.props.personName} - ${this.props.tmdbId}` });
|
||||
};
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
.movie {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
interface CssExports {
|
||||
'container': string;
|
||||
'grid': string;
|
||||
'movie': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
export default cssExports;
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { Navigation } from 'swiper';
|
||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||
import { Grid, WindowScroller } from 'react-virtualized';
|
||||
import Measure from 'Components/Measure';
|
||||
import dimensions from 'Styles/Variables/dimensions';
|
||||
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
|
||||
import MovieCreditPosterConnector from './MovieCreditPosterConnector';
|
||||
import styles from './MovieCreditPosters.css';
|
||||
|
||||
// Import Swiper styles
|
||||
import 'swiper/css';
|
||||
import 'swiper/css/navigation';
|
||||
|
||||
// Poster container dimensions
|
||||
const columnPadding = parseInt(dimensions.movieIndexColumnPadding);
|
||||
const columnPaddingSmallScreen = parseInt(dimensions.movieIndexColumnPaddingSmallScreen);
|
||||
@@ -68,11 +65,39 @@ class MovieCreditPosters extends Component {
|
||||
};
|
||||
|
||||
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;
|
||||
@@ -92,10 +117,7 @@ class MovieCreditPosters extends Component {
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
cellRenderer = ({ key, rowIndex, columnIndex, style }) => {
|
||||
const {
|
||||
items,
|
||||
itemComponent
|
||||
@@ -104,44 +126,99 @@ class MovieCreditPosters extends Component {
|
||||
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() {
|
||||
const {
|
||||
items
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
width,
|
||||
columnWidth,
|
||||
columnCount,
|
||||
rowHeight
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
const rowCount = Math.ceil(items.length / columnCount);
|
||||
|
||||
<div className={styles.sliderContainer}>
|
||||
<Swiper
|
||||
slidesPerView='auto'
|
||||
spaceBetween={10}
|
||||
slidesPerGroup={3}
|
||||
loop={false}
|
||||
loopFillGroupWithBlank={true}
|
||||
className="mySwiper"
|
||||
modules={[Navigation]}
|
||||
onInit={(swiper) => {
|
||||
swiper.params.navigation.prevEl = this._swiperPrevRef;
|
||||
swiper.params.navigation.nextEl = this._swiperNextRef;
|
||||
swiper.navigation.init();
|
||||
swiper.navigation.update();
|
||||
}}
|
||||
return (
|
||||
<Measure
|
||||
whitelist={['width']}
|
||||
onMeasure={this.onMeasure}
|
||||
>
|
||||
<WindowScroller
|
||||
scrollElement={undefined}
|
||||
>
|
||||
{items.map((credit) => (
|
||||
<SwiperSlide key={credit.id} style={{ width: posterWidth, height: rowHeight }}>
|
||||
<MovieCreditPosterConnector
|
||||
key={credit.id}
|
||||
component={itemComponent}
|
||||
posterWidth={posterWidth}
|
||||
posterHeight={posterHeight}
|
||||
tmdbId={credit.personTmdbId}
|
||||
personName={credit.personName}
|
||||
job={credit.job}
|
||||
character={credit.character}
|
||||
images={credit.images}
|
||||
/>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
{({ height, registerChild, onChildScroll, scrollTop }) => {
|
||||
if (!height) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={registerChild}>
|
||||
<Grid
|
||||
ref={this.setGridRef}
|
||||
className={styles.grid}
|
||||
autoHeight={true}
|
||||
height={height}
|
||||
columnCount={columnCount}
|
||||
columnWidth={columnWidth}
|
||||
rowCount={rowCount}
|
||||
rowHeight={rowHeight}
|
||||
width={width}
|
||||
onScroll={onChildScroll}
|
||||
scrollTop={scrollTop}
|
||||
overscanRowCount={2}
|
||||
cellRenderer={this.cellRenderer}
|
||||
scrollToAlignment={'start'}
|
||||
isScrollingOptOut={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
</WindowScroller>
|
||||
</Measure>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
3
frontend/src/Movie/Details/MovieAlternateTitles.css
Normal file
3
frontend/src/Movie/Details/MovieAlternateTitles.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.alternateTitle {
|
||||
white-space: nowrap;
|
||||
}
|
||||
28
frontend/src/Movie/Details/MovieAlternateTitles.js
Normal file
28
frontend/src/Movie/Details/MovieAlternateTitles.js
Normal file
@@ -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 {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 425px;
|
||||
height: 375px;
|
||||
}
|
||||
|
||||
.errorMessage {
|
||||
@@ -39,11 +39,10 @@
|
||||
}
|
||||
|
||||
.poster {
|
||||
z-index: 2;
|
||||
flex-shrink: 0;
|
||||
margin-right: 35px;
|
||||
width: 250px;
|
||||
height: 368px;
|
||||
width: 217px;
|
||||
height: 319px;
|
||||
}
|
||||
|
||||
.info {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
|
||||
import TextTruncate from 'react-text-truncate';
|
||||
import Alert from 'Components/Alert';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Icon from 'Components/Icon';
|
||||
import ImdbRating from 'Components/ImdbRating';
|
||||
import InfoLabel from 'Components/InfoLabel';
|
||||
@@ -23,11 +23,12 @@ import Popover from 'Components/Tooltip/Popover';
|
||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
|
||||
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
||||
import InteractiveSearchFilterMenuConnector from 'InteractiveSearch/InteractiveSearchFilterMenuConnector';
|
||||
import InteractiveSearchTable from 'InteractiveSearch/InteractiveSearchTable';
|
||||
import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal';
|
||||
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
||||
import MovieHistoryModal from 'Movie/History/MovieHistoryModal';
|
||||
import MovieHistoryTable from 'Movie/History/MovieHistoryTable';
|
||||
import MoviePoster from 'Movie/MoviePoster';
|
||||
import MovieInteractiveSearchModalConnector from 'Movie/Search/MovieInteractiveSearchModalConnector';
|
||||
import MovieFileEditorTable from 'MovieFile/Editor/MovieFileEditorTable';
|
||||
import ExtraFileTable from 'MovieFile/Extras/ExtraFileTable';
|
||||
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
|
||||
@@ -37,6 +38,8 @@ import * as keyCodes from 'Utilities/Constants/keyCodes';
|
||||
import formatRuntime from 'Utilities/Date/formatRuntime';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import selectAll from 'Utilities/Table/selectAll';
|
||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||
import MovieCollectionLabelConnector from './../MovieCollectionLabelConnector';
|
||||
import MovieCastPostersConnector from './Credits/Cast/MovieCastPostersConnector';
|
||||
import MovieCrewPostersConnector from './Credits/Crew/MovieCrewPostersConnector';
|
||||
@@ -54,6 +57,14 @@ function getFanartUrl(images) {
|
||||
return _.find(images, { coverType: 'fanart' })?.url;
|
||||
}
|
||||
|
||||
function getExpandedState(newState) {
|
||||
return {
|
||||
allExpanded: newState.allSelected,
|
||||
allCollapsed: newState.allUnselected,
|
||||
expandedState: newState.selectedState
|
||||
};
|
||||
}
|
||||
|
||||
class MovieDetails extends Component {
|
||||
|
||||
//
|
||||
@@ -67,8 +78,10 @@ class MovieDetails extends Component {
|
||||
isEditMovieModalOpen: false,
|
||||
isDeleteMovieModalOpen: false,
|
||||
isInteractiveImportModalOpen: false,
|
||||
isInteractiveSearchModalOpen: false,
|
||||
isMovieHistoryModalOpen: false,
|
||||
allExpanded: false,
|
||||
allCollapsed: false,
|
||||
expandedState: {},
|
||||
selectedTabIndex: 0,
|
||||
overviewHeight: 0,
|
||||
titleWidth: 0
|
||||
};
|
||||
@@ -101,6 +114,10 @@ class MovieDetails extends Component {
|
||||
this.setState({ isOrganizeModalOpen: false });
|
||||
};
|
||||
|
||||
onManageEpisodesPress = () => {
|
||||
this.setState({ isManageEpisodesOpen: true });
|
||||
};
|
||||
|
||||
onInteractiveImportPress = () => {
|
||||
this.setState({ isInteractiveImportModalOpen: true });
|
||||
};
|
||||
@@ -117,14 +134,6 @@ class MovieDetails extends Component {
|
||||
this.setState({ isEditMovieModalOpen: false });
|
||||
};
|
||||
|
||||
onInteractiveSearchPress = () => {
|
||||
this.setState({ isInteractiveSearchModalOpen: true });
|
||||
};
|
||||
|
||||
onInteractiveSearchModalClose = () => {
|
||||
this.setState({ isInteractiveSearchModalOpen: false });
|
||||
};
|
||||
|
||||
onDeleteMoviePress = () => {
|
||||
this.setState({
|
||||
isEditMovieModalOpen: false,
|
||||
@@ -136,12 +145,27 @@ class MovieDetails extends Component {
|
||||
this.setState({ isDeleteMovieModalOpen: false });
|
||||
};
|
||||
|
||||
onMovieHistoryPress = () => {
|
||||
this.setState({ isMovieHistoryModalOpen: true });
|
||||
onExpandAllPress = () => {
|
||||
const {
|
||||
allExpanded,
|
||||
expandedState
|
||||
} = this.state;
|
||||
|
||||
this.setState(getExpandedState(selectAll(expandedState, !allExpanded)));
|
||||
};
|
||||
|
||||
onMovieHistoryModalClose = () => {
|
||||
this.setState({ isMovieHistoryModalOpen: false });
|
||||
onExpandPress = (seasonNumber, isExpanded) => {
|
||||
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 }) => {
|
||||
@@ -180,12 +204,7 @@ class MovieDetails extends Component {
|
||||
if (
|
||||
touchStart < 50 ||
|
||||
this.props.isSidebarVisible ||
|
||||
this.state.isOrganizeModalOpen ||
|
||||
this.state.isEditMovieModalOpen ||
|
||||
this.state.isDeleteMovieModalOpen ||
|
||||
this.state.isInteractiveImportModalOpen ||
|
||||
this.state.isInteractiveSearchModalOpen ||
|
||||
this.state.isMovieHistoryModalOpen
|
||||
this.state.isEventModalOpen
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -220,6 +239,10 @@ class MovieDetails extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
onTabSelect = (index, lastIndex) => {
|
||||
this.setState({ selectedTabIndex: index });
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
@@ -263,7 +286,7 @@ class MovieDetails extends Component {
|
||||
onMonitorTogglePress,
|
||||
onRefreshPress,
|
||||
onSearchPress,
|
||||
queueItem,
|
||||
queueItems,
|
||||
movieRuntimeFormat
|
||||
} = this.props;
|
||||
|
||||
@@ -272,10 +295,9 @@ class MovieDetails extends Component {
|
||||
isEditMovieModalOpen,
|
||||
isDeleteMovieModalOpen,
|
||||
isInteractiveImportModalOpen,
|
||||
isInteractiveSearchModalOpen,
|
||||
isMovieHistoryModalOpen,
|
||||
overviewHeight,
|
||||
titleWidth
|
||||
titleWidth,
|
||||
selectedTabIndex
|
||||
} = this.state;
|
||||
|
||||
const fanartUrl = getFanartUrl(images);
|
||||
@@ -302,14 +324,6 @@ class MovieDetails extends Component {
|
||||
onPress={onSearchPress}
|
||||
/>
|
||||
|
||||
<PageToolbarButton
|
||||
label={translate('InteractiveSearch')}
|
||||
iconName={icons.INTERACTIVE}
|
||||
isSpinning={isSearching}
|
||||
title={undefined}
|
||||
onPress={this.onInteractiveSearchPress}
|
||||
/>
|
||||
|
||||
<PageToolbarSeparator />
|
||||
|
||||
<PageToolbarButton
|
||||
@@ -325,12 +339,6 @@ class MovieDetails extends Component {
|
||||
onPress={this.onInteractiveImportPress}
|
||||
/>
|
||||
|
||||
<PageToolbarButton
|
||||
label={translate('History')}
|
||||
iconName={icons.HISTORY}
|
||||
onPress={this.onMovieHistoryPress}
|
||||
/>
|
||||
|
||||
<PageToolbarSeparator />
|
||||
|
||||
<PageToolbarButton
|
||||
@@ -536,7 +544,7 @@ class MovieDetails extends Component {
|
||||
hasMovieFiles={hasMovieFiles}
|
||||
monitored={monitored}
|
||||
isAvailable={isAvailable}
|
||||
queueItem={queueItem}
|
||||
queueItem={(queueItems.length > 0) ? queueItems[0] : null}
|
||||
/>
|
||||
</span>
|
||||
</InfoLabel>
|
||||
@@ -646,33 +654,101 @@ class MovieDetails extends Component {
|
||||
null
|
||||
}
|
||||
|
||||
<FieldSet legend={translate('Files')}>
|
||||
<MovieFileEditorTable
|
||||
movieId={id}
|
||||
/>
|
||||
<Tabs selectedIndex={selectedTabIndex} onSelect={this.onTabSelect}>
|
||||
<TabList
|
||||
className={styles.tabList}
|
||||
>
|
||||
<Tab
|
||||
className={styles.tab}
|
||||
selectedClassName={styles.selectedTab}
|
||||
>
|
||||
{translate('History')}
|
||||
</Tab>
|
||||
|
||||
<ExtraFileTable
|
||||
movieId={id}
|
||||
/>
|
||||
</FieldSet>
|
||||
<Tab
|
||||
className={styles.tab}
|
||||
selectedClassName={styles.selectedTab}
|
||||
>
|
||||
{translate('Search')}
|
||||
</Tab>
|
||||
|
||||
<FieldSet legend={translate('Cast')}>
|
||||
<MovieCastPostersConnector
|
||||
isSmallScreen={isSmallScreen}
|
||||
/>
|
||||
</FieldSet>
|
||||
<Tab
|
||||
className={styles.tab}
|
||||
selectedClassName={styles.selectedTab}
|
||||
>
|
||||
{translate('Files')}
|
||||
</Tab>
|
||||
|
||||
<FieldSet legend={translate('Crew')}>
|
||||
<MovieCrewPostersConnector
|
||||
isSmallScreen={isSmallScreen}
|
||||
/>
|
||||
</FieldSet>
|
||||
<Tab
|
||||
className={styles.tab}
|
||||
selectedClassName={styles.selectedTab}
|
||||
>
|
||||
{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>
|
||||
|
||||
<OrganizePreviewModalConnector
|
||||
@@ -688,12 +764,6 @@ class MovieDetails extends Component {
|
||||
onDeleteMoviePress={this.onDeleteMoviePress}
|
||||
/>
|
||||
|
||||
<MovieHistoryModal
|
||||
isOpen={isMovieHistoryModalOpen}
|
||||
movieId={id}
|
||||
onModalClose={this.onMovieHistoryModalClose}
|
||||
/>
|
||||
|
||||
<DeleteMovieModal
|
||||
isOpen={isDeleteMovieModalOpen}
|
||||
movieId={id}
|
||||
@@ -710,12 +780,6 @@ class MovieDetails extends Component {
|
||||
showImportMode={false}
|
||||
onModalClose={this.onInteractiveImportModalClose}
|
||||
/>
|
||||
|
||||
<MovieInteractiveSearchModalConnector
|
||||
isOpen={isInteractiveSearchModalOpen}
|
||||
movieId={id}
|
||||
onModalClose={this.onInteractiveSearchModalClose}
|
||||
/>
|
||||
</PageContentBody>
|
||||
</PageContent>
|
||||
);
|
||||
@@ -766,7 +830,7 @@ MovieDetails.propTypes = {
|
||||
onRefreshPress: PropTypes.func.isRequired,
|
||||
onSearchPress: PropTypes.func.isRequired,
|
||||
onGoToMovie: PropTypes.func.isRequired,
|
||||
queueItem: PropTypes.object,
|
||||
queueItems: PropTypes.arrayOf(PropTypes.object),
|
||||
movieRuntimeFormat: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import { toggleMovieMonitored } from 'Store/Actions/movieActions';
|
||||
import { clearMovieBlocklist, fetchMovieBlocklist } from 'Store/Actions/movieBlocklistActions';
|
||||
import { clearMovieCredits, fetchMovieCredits } from 'Store/Actions/movieCreditsActions';
|
||||
import { clearMovieFiles, fetchMovieFiles } from 'Store/Actions/movieFileActions';
|
||||
import { clearMovieHistory, fetchMovieHistory } from 'Store/Actions/movieHistoryActions';
|
||||
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
|
||||
import { cancelFetchReleases, clearReleases } from 'Store/Actions/releaseActions';
|
||||
import { fetchImportListSchema } from 'Store/Actions/settingsActions';
|
||||
@@ -144,8 +145,6 @@ function createMapStateToProps() {
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const queueItem = queueItems.find((item) => item.movieId === movie.id);
|
||||
|
||||
return {
|
||||
...movie,
|
||||
alternateTitles,
|
||||
@@ -166,7 +165,7 @@ function createMapStateToProps() {
|
||||
nextMovie,
|
||||
isSmallScreen: dimensions.isSmallScreen,
|
||||
isSidebarVisible,
|
||||
queueItem,
|
||||
queueItems,
|
||||
movieRuntimeFormat
|
||||
};
|
||||
}
|
||||
@@ -181,6 +180,12 @@ function createMapDispatchToProps(dispatch, props) {
|
||||
dispatchClearMovieFiles() {
|
||||
dispatch(clearMovieFiles());
|
||||
},
|
||||
dispatchFetchMovieHistory({ movieId }) {
|
||||
dispatch(fetchMovieHistory({ movieId }));
|
||||
},
|
||||
dispatchClearMovieHistory() {
|
||||
dispatch(clearMovieHistory());
|
||||
},
|
||||
dispatchFetchMovieCredits({ movieId }) {
|
||||
dispatch(fetchMovieCredits({ movieId }));
|
||||
},
|
||||
@@ -276,6 +281,7 @@ class MovieDetailsConnector extends Component {
|
||||
|
||||
this.props.dispatchFetchMovieFiles({ movieId });
|
||||
this.props.dispatchFetchMovieBlocklist({ movieId });
|
||||
this.props.dispatchFetchMovieHistory({ movieId });
|
||||
this.props.dispatchFetchExtraFiles({ movieId });
|
||||
this.props.dispatchFetchMovieCredits({ movieId });
|
||||
this.props.dispatchFetchQueueDetails({ movieId });
|
||||
@@ -286,6 +292,7 @@ class MovieDetailsConnector extends Component {
|
||||
this.props.dispatchCancelFetchReleases();
|
||||
this.props.dispatchClearMovieBlocklist();
|
||||
this.props.dispatchClearMovieFiles();
|
||||
this.props.dispatchClearMovieHistory();
|
||||
this.props.dispatchClearExtraFiles();
|
||||
this.props.dispatchClearMovieCredits();
|
||||
this.props.dispatchClearQueueDetails();
|
||||
@@ -342,6 +349,8 @@ MovieDetailsConnector.propTypes = {
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
dispatchFetchMovieFiles: PropTypes.func.isRequired,
|
||||
dispatchClearMovieFiles: PropTypes.func.isRequired,
|
||||
dispatchFetchMovieHistory: PropTypes.func.isRequired,
|
||||
dispatchClearMovieHistory: PropTypes.func.isRequired,
|
||||
dispatchFetchExtraFiles: PropTypes.func.isRequired,
|
||||
dispatchClearExtraFiles: PropTypes.func.isRequired,
|
||||
dispatchFetchMovieCredits: PropTypes.func.isRequired,
|
||||
|
||||
@@ -8,6 +8,7 @@ import translate from 'Utilities/String/translate';
|
||||
import styles from './MovieStatusLabel.css';
|
||||
|
||||
function getMovieStatus(hasFile, isMonitored, isAvailable, queueItem = false) {
|
||||
|
||||
if (queueItem) {
|
||||
const queueStatus = queueItem.status;
|
||||
const queueState = queueItem.trackedDownloadStatus;
|
||||
@@ -115,4 +116,8 @@ MovieStatusLabel.propTypes = {
|
||||
colorImpairedMode: PropTypes.bool
|
||||
};
|
||||
|
||||
MovieStatusLabel.defaultProps = {
|
||||
title: ''
|
||||
};
|
||||
|
||||
export default MovieStatusLabel;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import MovieTitlesTableContentConnector from './MovieTitlesTableContentConnector';
|
||||
import styles from './MovieTitlesTable.css';
|
||||
|
||||
function MovieTitlesTable(props) {
|
||||
const {
|
||||
@@ -8,11 +7,9 @@ function MovieTitlesTable(props) {
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<MovieTitlesTableContentConnector
|
||||
{...otherProps}
|
||||
/>
|
||||
</div>
|
||||
<MovieTitlesTableContentConnector
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,142 +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',
|
||||
label: () => translate('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;
|
||||
@@ -1,4 +1,5 @@
|
||||
.container {
|
||||
margin-top: 20px;
|
||||
border: 1px solid var(--borderColor);
|
||||
border-radius: 4px;
|
||||
background-color: var(--inputBackgroundColor);
|
||||
22
frontend/src/Movie/History/MovieHistoryTable.js
Normal file
22
frontend/src/Movie/History/MovieHistoryTable.js
Normal file
@@ -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;
|
||||
128
frontend/src/Movie/History/MovieHistoryTableContent.js
Normal file
128
frontend/src/Movie/History/MovieHistoryTableContent.js
Normal file
@@ -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;
|
||||
@@ -2,8 +2,8 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { clearMovieHistory, fetchMovieHistory, movieHistoryMarkAsFailed } from 'Store/Actions/movieHistoryActions';
|
||||
import MovieHistoryModalContent from './MovieHistoryModalContent';
|
||||
import { movieHistoryMarkAsFailed } from 'Store/Actions/movieHistoryActions';
|
||||
import MovieHistoryTableContent from './MovieHistoryTableContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
@@ -15,29 +15,10 @@ function createMapStateToProps() {
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchMovieHistory,
|
||||
clearMovieHistory,
|
||||
movieHistoryMarkAsFailed
|
||||
};
|
||||
|
||||
class MovieHistoryModalContentConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
movieId
|
||||
} = this.props;
|
||||
|
||||
this.props.fetchMovieHistory({
|
||||
movieId
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.clearMovieHistory();
|
||||
}
|
||||
class MovieHistoryTableContentConnector extends Component {
|
||||
|
||||
//
|
||||
// Listeners
|
||||
@@ -58,7 +39,7 @@ class MovieHistoryModalContentConnector extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<MovieHistoryModalContent
|
||||
<MovieHistoryTableContent
|
||||
{...this.props}
|
||||
onMarkAsFailedPress={this.onMarkAsFailedPress}
|
||||
/>
|
||||
@@ -66,11 +47,9 @@ class MovieHistoryModalContentConnector extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
MovieHistoryModalContentConnector.propTypes = {
|
||||
MovieHistoryTableContentConnector.propTypes = {
|
||||
movieId: PropTypes.number.isRequired,
|
||||
fetchMovieHistory: PropTypes.func.isRequired,
|
||||
clearMovieHistory: PropTypes.func.isRequired,
|
||||
movieHistoryMarkAsFailed: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(MovieHistoryModalContentConnector);
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(MovieHistoryTableContentConnector);
|
||||
@@ -26,7 +26,7 @@ import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
import { SelectStateInputProps } from 'typings/props';
|
||||
import formatRuntime from 'Utilities/Date/formatRuntime';
|
||||
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 MovieIndexProgressBar from '../ProgressBar/MovieIndexProgressBar';
|
||||
import MovieStatusCell from './MovieStatusCell';
|
||||
@@ -286,7 +286,7 @@ function MovieIndexRow(props: MovieIndexRowProps) {
|
||||
if (name === 'minimumAvailability') {
|
||||
return (
|
||||
<VirtualTableRowCell key={name} className={styles[name]}>
|
||||
{translate(firstCharToUpper(minimumAvailability))}
|
||||
{titleCase(minimumAvailability)}
|
||||
</VirtualTableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,10 +11,7 @@ function createMovieQueueDetailsSelector(movieId: number) {
|
||||
(queueItems) => {
|
||||
return queueItems.reduce(
|
||||
(acc: MovieQueueDetails, item) => {
|
||||
if (
|
||||
item.trackedDownloadState === 'imported' ||
|
||||
item.movieId !== movieId
|
||||
) {
|
||||
if (item.movieId !== movieId) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import MovieInteractiveSearchModalContent from './MovieInteractiveSearchModalContent';
|
||||
|
||||
function MovieInteractiveSearchModal(props) {
|
||||
const {
|
||||
isOpen,
|
||||
movieId,
|
||||
onModalClose
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
closeOnBackgroundClick={false}
|
||||
onModalClose={onModalClose}
|
||||
size={sizes.EXTRA_EXTRA_LARGE}
|
||||
>
|
||||
<MovieInteractiveSearchModalContent
|
||||
movieId={movieId}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
MovieInteractiveSearchModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
movieId: PropTypes.number.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default MovieInteractiveSearchModal;
|
||||
@@ -1,59 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { cancelFetchReleases, clearReleases } from 'Store/Actions/releaseActions';
|
||||
import MovieInteractiveSearchModal from './MovieInteractiveSearchModal';
|
||||
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
dispatchCancelFetchReleases() {
|
||||
dispatch(cancelFetchReleases());
|
||||
},
|
||||
|
||||
dispatchClearReleases() {
|
||||
dispatch(clearReleases());
|
||||
},
|
||||
|
||||
onModalClose() {
|
||||
dispatch(cancelFetchReleases());
|
||||
dispatch(clearReleases());
|
||||
props.onModalClose();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class MovieInteractiveSearchModalConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.dispatchCancelFetchReleases();
|
||||
this.props.dispatchClearReleases();
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
dispatchCancelFetchReleases,
|
||||
dispatchClearReleases,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<MovieInteractiveSearchModal
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MovieInteractiveSearchModalConnector.propTypes = {
|
||||
...MovieInteractiveSearchModal.propTypes,
|
||||
dispatchCancelFetchReleases: PropTypes.func.isRequired,
|
||||
dispatchClearReleases: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(null, createMapDispatchToProps)(MovieInteractiveSearchModalConnector);
|
||||
@@ -1,44 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Button from 'Components/Link/Button';
|
||||
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 { scrollDirections } from 'Helpers/Props';
|
||||
import InteractiveSearchConnector from 'InteractiveSearch/InteractiveSearchConnector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
function MovieInteractiveSearchModalContent(props) {
|
||||
const {
|
||||
movieId,
|
||||
onModalClose
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{translate('InteractiveSearchModalHeader')}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody scrollDirection={scrollDirections.BOTH}>
|
||||
<InteractiveSearchConnector
|
||||
searchPayload={{ movieId }}
|
||||
/>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={onModalClose}>
|
||||
{translate('Close')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
|
||||
MovieInteractiveSearchModalContent.propTypes = {
|
||||
movieId: PropTypes.number.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default MovieInteractiveSearchModalContent;
|
||||
@@ -1,4 +1,5 @@
|
||||
.container {
|
||||
margin-top: 20px;
|
||||
border: 1px solid var(--borderColor);
|
||||
border-radius: 4px;
|
||||
background-color: var(--inputBackgroundColor);
|
||||
|
||||
@@ -152,7 +152,7 @@ class CustomFormat extends Component {
|
||||
isOpen={this.state.isDeleteCustomFormatModalOpen}
|
||||
kind={kinds.DANGER}
|
||||
title={translate('DeleteCustomFormat')}
|
||||
message={translate('DeleteCustomFormatMessageText', { customFormatName: name })}
|
||||
message={translate('DeleteCustomFormatMessageText', { name })}
|
||||
confirmLabel={translate('Delete')}
|
||||
isSpinning={isDeleting}
|
||||
onConfirm={this.onConfirmDeleteCustomFormat}
|
||||
|
||||
@@ -124,7 +124,6 @@ class SecuritySettings extends Component {
|
||||
authenticationRequired,
|
||||
username,
|
||||
password,
|
||||
passwordConfirmation,
|
||||
apiKey,
|
||||
certificateValidation
|
||||
} = settings;
|
||||
@@ -140,8 +139,8 @@ class SecuritySettings extends Component {
|
||||
type={inputTypes.SELECT}
|
||||
name="authenticationMethod"
|
||||
values={authenticationMethodOptions}
|
||||
helpText={translate('AuthenticationMethodHelpText')}
|
||||
helpTextWarning={translate('AuthenticationRequiredWarning')}
|
||||
helpText={translate('AuthenticationMethodHelpText', { appName: 'Radarr' })}
|
||||
helpTextWarning={translate('AuthenticationRequiredWarning', { appName: 'Radarr' })}
|
||||
onChange={onInputChange}
|
||||
{...authenticationMethod}
|
||||
/>
|
||||
@@ -194,21 +193,6 @@ class SecuritySettings extends Component {
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
authenticationEnabled ?
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('PasswordConfirmation')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.PASSWORD}
|
||||
name="passwordConfirmation"
|
||||
onChange={onInputChange}
|
||||
{...passwordConfirmation}
|
||||
/>
|
||||
</FormGroup> :
|
||||
null
|
||||
}
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('ApiKey')}</FormLabel>
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ function UpdateSettings(props) {
|
||||
type={inputTypes.CHECK}
|
||||
name="updateAutomatically"
|
||||
helpText={translate('UpdateAutomaticallyHelpText')}
|
||||
helpTextWarning={updateMechanism.value === 'docker' ? translate('AutomaticUpdatesDisabledDocker') : undefined}
|
||||
helpTextWarning={updateMechanism.value === 'docker' ? translate('AutomaticUpdatesDisabledDocker', { appName: 'Radarr' }) : undefined}
|
||||
onChange={onInputChange}
|
||||
{...updateAutomatically}
|
||||
/>
|
||||
|
||||
@@ -74,15 +74,9 @@ class ImportList extends Component {
|
||||
<div className={styles.enabled}>
|
||||
|
||||
{
|
||||
enabled ?
|
||||
enabled &&
|
||||
<Label kind={kinds.SUCCESS}>
|
||||
{translate('Enabled')}
|
||||
</Label> :
|
||||
<Label
|
||||
kind={kinds.DISABLED}
|
||||
outline={true}
|
||||
>
|
||||
{translate('Disabled')}
|
||||
</Label>
|
||||
}
|
||||
|
||||
@@ -92,6 +86,16 @@ class ImportList extends Component {
|
||||
{translate('Auto')}
|
||||
</Label>
|
||||
}
|
||||
|
||||
{
|
||||
!enabled && !enableAuto &&
|
||||
<Label
|
||||
kind={kinds.DISABLED}
|
||||
outline={true}
|
||||
>
|
||||
{translate('Disabled')}
|
||||
</Label>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className={styles.enabled}>
|
||||
|
||||
@@ -12,10 +12,8 @@ import translate from 'Utilities/String/translate';
|
||||
import styles from './ManageImportListsEditModalContent.css';
|
||||
|
||||
interface SavePayload {
|
||||
enabled?: boolean;
|
||||
enableAuto?: boolean;
|
||||
qualityProfileId?: number;
|
||||
minimumAvailability?: string;
|
||||
rootFolderPath?: string;
|
||||
}
|
||||
|
||||
@@ -27,7 +25,7 @@ interface ManageImportListsEditModalContentProps {
|
||||
|
||||
const NO_CHANGE = 'noChange';
|
||||
|
||||
const enableOptions = [
|
||||
const autoAddOptions = [
|
||||
{
|
||||
key: NO_CHANGE,
|
||||
get value() {
|
||||
@@ -54,23 +52,16 @@ function ManageImportListsEditModalContent(
|
||||
) {
|
||||
const { importListIds, onSavePress, onModalClose } = props;
|
||||
|
||||
const [enabled, setEnabled] = useState(NO_CHANGE);
|
||||
const [enableAuto, setEnableAuto] = useState(NO_CHANGE);
|
||||
const [qualityProfileId, setQualityProfileId] = useState<string | number>(
|
||||
NO_CHANGE
|
||||
);
|
||||
const [minimumAvailability, setMinimumAvailability] = useState(NO_CHANGE);
|
||||
const [rootFolderPath, setRootFolderPath] = useState(NO_CHANGE);
|
||||
|
||||
const save = useCallback(() => {
|
||||
let hasChanges = false;
|
||||
const payload: SavePayload = {};
|
||||
|
||||
if (enabled !== NO_CHANGE) {
|
||||
hasChanges = true;
|
||||
payload.enabled = enabled === 'enabled';
|
||||
}
|
||||
|
||||
if (enableAuto !== NO_CHANGE) {
|
||||
hasChanges = true;
|
||||
payload.enableAuto = enableAuto === 'enabled';
|
||||
@@ -81,11 +72,6 @@ function ManageImportListsEditModalContent(
|
||||
payload.qualityProfileId = qualityProfileId as number;
|
||||
}
|
||||
|
||||
if (minimumAvailability !== NO_CHANGE) {
|
||||
hasChanges = true;
|
||||
payload.minimumAvailability = minimumAvailability as string;
|
||||
}
|
||||
|
||||
if (rootFolderPath !== NO_CHANGE) {
|
||||
hasChanges = true;
|
||||
payload.rootFolderPath = rootFolderPath;
|
||||
@@ -96,31 +82,17 @@ function ManageImportListsEditModalContent(
|
||||
}
|
||||
|
||||
onModalClose();
|
||||
}, [
|
||||
enabled,
|
||||
enableAuto,
|
||||
qualityProfileId,
|
||||
minimumAvailability,
|
||||
rootFolderPath,
|
||||
onSavePress,
|
||||
onModalClose,
|
||||
]);
|
||||
}, [enableAuto, qualityProfileId, rootFolderPath, onSavePress, onModalClose]);
|
||||
|
||||
const onInputChange = useCallback(
|
||||
({ name, value }: { name: string; value: string }) => {
|
||||
switch (name) {
|
||||
case 'enabled':
|
||||
setEnabled(value);
|
||||
break;
|
||||
case 'enableAuto':
|
||||
setEnableAuto(value);
|
||||
break;
|
||||
case 'qualityProfileId':
|
||||
setQualityProfileId(value);
|
||||
break;
|
||||
case 'minimumAvailability':
|
||||
setMinimumAvailability(value);
|
||||
break;
|
||||
case 'rootFolderPath':
|
||||
setRootFolderPath(value);
|
||||
break;
|
||||
@@ -138,18 +110,6 @@ function ManageImportListsEditModalContent(
|
||||
<ModalHeader>{translate('EditSelectedImportLists')}</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Enabled')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name="enabled"
|
||||
value={enabled}
|
||||
values={enableOptions}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('AutomaticAdd')}</FormLabel>
|
||||
|
||||
@@ -157,7 +117,7 @@ function ManageImportListsEditModalContent(
|
||||
type={inputTypes.SELECT}
|
||||
name="enableAuto"
|
||||
value={enableAuto}
|
||||
values={enableOptions}
|
||||
values={autoAddOptions}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
@@ -175,19 +135,6 @@ function ManageImportListsEditModalContent(
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('MinimumAvailability')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.AVAILABILITY_SELECT}
|
||||
name="minimumAvailability"
|
||||
value={minimumAvailability}
|
||||
includeNoChange={true}
|
||||
includeNoChangeDisabled={false}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('RootFolder')}</FormLabel>
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import ManageImportListsModalContent from './ManageImportListsModalContent';
|
||||
|
||||
interface ManageImportListsModalProps {
|
||||
@@ -12,7 +11,7 @@ function ManageImportListsModal(props: ManageImportListsModalProps) {
|
||||
const { isOpen, onModalClose } = props;
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} size={sizes.EXTRA_LARGE} onModalClose={onModalClose}>
|
||||
<Modal isOpen={isOpen} onModalClose={onModalClose}>
|
||||
<ManageImportListsModalContent onModalClose={onModalClose} />
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -52,24 +52,12 @@ const COLUMNS = [
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'minimumAvailability',
|
||||
label: () => translate('MinimumAvailability'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'rootFolderPath',
|
||||
label: () => translate('RootFolder'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'enabled',
|
||||
label: () => translate('Enabled'),
|
||||
isSortable: true,
|
||||
isVisible: true,
|
||||
},
|
||||
{
|
||||
name: 'enableAuto',
|
||||
label: () => translate('AutomaticAdd'),
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
.name,
|
||||
.tags,
|
||||
.enabled,
|
||||
.enableAuto,
|
||||
.minimumAvailability,
|
||||
.qualityProfileId,
|
||||
.rootFolderPath,
|
||||
.implementation {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,7 @@
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'enableAuto': string;
|
||||
'enabled': string;
|
||||
'implementation': string;
|
||||
'minimumAvailability': string;
|
||||
'name': string;
|
||||
'qualityProfileId': string;
|
||||
'rootFolderPath': string;
|
||||
|
||||
@@ -7,8 +7,6 @@ import TableRow from 'Components/Table/TableRow';
|
||||
import TagListConnector from 'Components/TagListConnector';
|
||||
import { createQualityProfileSelectorForHook } from 'Store/Selectors/createQualityProfileSelector';
|
||||
import { SelectStateInputProps } from 'typings/props';
|
||||
import firstCharToUpper from 'Utilities/String/firstCharToUpper';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './ManageImportListsModalRow.css';
|
||||
|
||||
interface ManageImportListsModalRowProps {
|
||||
@@ -16,10 +14,8 @@ interface ManageImportListsModalRowProps {
|
||||
name: string;
|
||||
rootFolderPath: string;
|
||||
qualityProfileId: number;
|
||||
minimumAvailability: string;
|
||||
implementation: string;
|
||||
tags: number[];
|
||||
enabled: boolean;
|
||||
enableAuto: boolean;
|
||||
columns: Column[];
|
||||
isSelected?: boolean;
|
||||
@@ -32,10 +28,8 @@ function ManageImportListsModalRow(props: ManageImportListsModalRowProps) {
|
||||
isSelected,
|
||||
name,
|
||||
rootFolderPath,
|
||||
minimumAvailability,
|
||||
qualityProfileId,
|
||||
implementation,
|
||||
enabled,
|
||||
enableAuto,
|
||||
tags,
|
||||
onSelectedChange,
|
||||
@@ -69,23 +63,15 @@ function ManageImportListsModalRow(props: ManageImportListsModalRowProps) {
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.qualityProfileId}>
|
||||
{qualityProfile?.name ?? translate('None')}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.minimumAvailability}>
|
||||
{translate(firstCharToUpper(minimumAvailability))}
|
||||
{qualityProfile?.name ?? 'None'}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.rootFolderPath}>
|
||||
{rootFolderPath}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.enabled}>
|
||||
{enabled ? translate('Yes') : translate('No')}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.enableAuto}>
|
||||
{enableAuto ? translate('Yes') : translate('No')}
|
||||
{enableAuto ? 'Yes' : 'No'}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.tags}>
|
||||
|
||||
@@ -122,7 +122,7 @@ export default {
|
||||
return selectProviderSchema(state, section, payload, (selectedSchema) => {
|
||||
selectedSchema.name = payload.presetName ?? payload.implementationName;
|
||||
selectedSchema.implementationName = payload.implementationName;
|
||||
selectedSchema.minRefreshInterval = selectedSchema.minRefreshInterval ?? payload.minRefreshInterval;
|
||||
selectedSchema.minRefreshInterval = payload.minRefreshInterval;
|
||||
selectedSchema.minimumAvailability = 'released';
|
||||
selectedSchema.rootFolderPath = '';
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment/moment';
|
||||
import { createAction } from 'redux-actions';
|
||||
import { batchActions } from 'redux-batched-actions';
|
||||
import { filterBuilderTypes, filterBuilderValueTypes, filterTypes, sortDirections } from 'Helpers/Props';
|
||||
@@ -10,7 +9,7 @@ import getNewMovie from 'Utilities/Movie/getNewMovie';
|
||||
import getSectionState from 'Utilities/State/getSectionState';
|
||||
import updateSectionState from 'Utilities/State/updateSectionState';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import { removeItem, set, update, updateItem } from './baseActions';
|
||||
import { removeItem, set, updateItem } from './baseActions';
|
||||
import createHandleActions from './Creators/createHandleActions';
|
||||
import createClearReducer from './Creators/Reducers/createClearReducer';
|
||||
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
|
||||
@@ -220,42 +219,6 @@ export const defaultState = {
|
||||
const { ratings = {} } = item;
|
||||
|
||||
return ratings.tmdb? ratings.tmdb.value : 0;
|
||||
},
|
||||
|
||||
inCinemas: function(item, direction) {
|
||||
if (item.inCinemas) {
|
||||
return moment(item.inCinemas).unix();
|
||||
}
|
||||
|
||||
if (direction === sortDirections.DESCENDING) {
|
||||
return -1 * Number.MAX_VALUE;
|
||||
}
|
||||
|
||||
return Number.MAX_VALUE;
|
||||
},
|
||||
|
||||
physicalRelease: function(item, direction) {
|
||||
if (item.physicalRelease) {
|
||||
return moment(item.physicalRelease).unix();
|
||||
}
|
||||
|
||||
if (direction === sortDirections.DESCENDING) {
|
||||
return -1 * Number.MAX_VALUE;
|
||||
}
|
||||
|
||||
return Number.MAX_VALUE;
|
||||
},
|
||||
|
||||
digitalRelease: function(item, direction) {
|
||||
if (item.digitalRelease) {
|
||||
return moment(item.digitalRelease).unix();
|
||||
}
|
||||
|
||||
if (direction === sortDirections.DESCENDING) {
|
||||
return -1 * Number.MAX_VALUE;
|
||||
}
|
||||
|
||||
return Number.MAX_VALUE;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -544,11 +507,11 @@ export const actionHandlers = handleThunks({
|
||||
}).request;
|
||||
|
||||
promise.done((data) => {
|
||||
// set an ID so the selectors and updaters done blow up.
|
||||
// set an Id so the selectors and updaters done blow up.
|
||||
data = data.map((movie) => ({ ...movie, id: movie.tmdbId }));
|
||||
|
||||
dispatch(batchActions([
|
||||
update({ section, data }),
|
||||
...data.map((movie) => updateItem({ section, ...movie })),
|
||||
|
||||
set({
|
||||
section,
|
||||
|
||||
@@ -91,7 +91,7 @@ export const defaultState = {
|
||||
columnLabel: () => translate('CustomFormatScore'),
|
||||
label: React.createElement(Icon, {
|
||||
name: icons.SCORE,
|
||||
title: () => translate('CustomFormatScore')
|
||||
title: 'Custom format score'
|
||||
}),
|
||||
isVisible: false
|
||||
},
|
||||
|
||||
@@ -28,8 +28,8 @@ export const defaultState = {
|
||||
isReprocessing: false,
|
||||
error: null,
|
||||
items: [],
|
||||
sortKey: 'relativePath',
|
||||
sortDirection: sortDirections.ASCENDING,
|
||||
sortKey: 'quality',
|
||||
sortDirection: sortDirections.DESCENDING,
|
||||
recentFolders: [],
|
||||
importMode: 'chooseImportMode',
|
||||
sortPredicates: {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import _ from 'lodash';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
function createMovieCollectionListSelector() {
|
||||
return createSelector(
|
||||
(state, { tmdbId }) => tmdbId,
|
||||
(state) => state.settings.importLists.items,
|
||||
(tmdbId, importLists) => {
|
||||
const importListIds = _.reduce(importLists, (acc, list) => {
|
||||
if (list.implementation === 'TMDbCollectionImport') {
|
||||
const collectionIdField = list.fields.find((field) => {
|
||||
return field.name === 'collectionId';
|
||||
});
|
||||
|
||||
if (collectionIdField && parseInt(collectionIdField.value) === tmdbId) {
|
||||
acc.push(list);
|
||||
return acc;
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
if (importListIds.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return importListIds[0];
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default createMovieCollectionListSelector;
|
||||
@@ -21,11 +21,15 @@ function createMovieCreditListSelector() {
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
if (importListIds.length === 0) {
|
||||
return undefined;
|
||||
let importListId = 0;
|
||||
|
||||
if (importListIds.length > 0) {
|
||||
importListId = importListIds[0].id;
|
||||
}
|
||||
|
||||
return importListIds[0];
|
||||
return {
|
||||
importListId
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ module.exports = {
|
||||
// Toolbar
|
||||
toolbarColor: '#e1e2e3',
|
||||
toolbarBackgroundColor: '#262626',
|
||||
toolbarMenuItemBackgroundColor: '#303030',
|
||||
toolbarMenuItemBackgroundColor: '#606060',
|
||||
toolbarMenuItemHoverBackgroundColor: '#515151',
|
||||
toolbarLabelColor: '#e1e2e3',
|
||||
|
||||
|
||||
@@ -116,7 +116,6 @@ class BackupRow extends Component {
|
||||
|
||||
<TableRowCell className={styles.actions}>
|
||||
<IconButton
|
||||
title={translate('RestoreBackup')}
|
||||
name={icons.RESTORE}
|
||||
onPress={this.onRestorePress}
|
||||
/>
|
||||
@@ -139,9 +138,7 @@ class BackupRow extends Component {
|
||||
isOpen={isConfirmDeleteModalOpen}
|
||||
kind={kinds.DANGER}
|
||||
title={translate('DeleteBackup')}
|
||||
message={translate('DeleteBackupMessageText', {
|
||||
name
|
||||
})}
|
||||
message={translate('DeleteBackupMessageText', { name })}
|
||||
confirmLabel={translate('Delete')}
|
||||
onConfirm={this.onConfirmDeletePress}
|
||||
onCancel={this.onConfirmDeleteModalClose}
|
||||
|
||||
@@ -109,7 +109,7 @@ class Backups extends Component {
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('BackupsLoadError')}
|
||||
{translate('UnableToLoadBackups')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
|
||||
@@ -146,9 +146,7 @@ class RestoreBackupModalContent extends Component {
|
||||
|
||||
<ModalBody>
|
||||
{
|
||||
!!id && translate('WouldYouLikeToRestoreBackup', {
|
||||
name
|
||||
})
|
||||
!!id && translate('WouldYouLikeToRestoreBackup', { name })
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import Link from 'Components/Link/Link';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||
@@ -67,7 +67,7 @@ class LogFiles extends Component {
|
||||
/>
|
||||
|
||||
<PageToolbarButton
|
||||
label={translate('Clear')}
|
||||
label={translate('Delete')}
|
||||
iconName={icons.CLEAR}
|
||||
isSpinning={deleteFilesExecuting}
|
||||
onPress={onDeleteFilesPress}
|
||||
@@ -77,15 +77,13 @@ class LogFiles extends Component {
|
||||
<PageContentBody>
|
||||
<Alert>
|
||||
<div>
|
||||
{translate('LogFilesLocation', {
|
||||
location
|
||||
})}
|
||||
Log files are located in: {location}
|
||||
</div>
|
||||
|
||||
{
|
||||
currentLogView === 'Log Files' &&
|
||||
<div>
|
||||
<InlineMarkdown data={translate('TheLogLevelDefault')} />
|
||||
{translate('TheLogLevelDefault')} <Link to="/settings/general">{translate('GeneralSettings')}</Link>
|
||||
</div>
|
||||
}
|
||||
</Alert>
|
||||
|
||||
@@ -4,7 +4,6 @@ import Menu from 'Components/Menu/Menu';
|
||||
import MenuButton from 'Components/Menu/MenuButton';
|
||||
import MenuContent from 'Components/Menu/MenuContent';
|
||||
import MenuItem from 'Components/Menu/MenuItem';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
class LogsNavMenu extends Component {
|
||||
|
||||
@@ -51,13 +50,13 @@ class LogsNavMenu extends Component {
|
||||
<MenuItem
|
||||
to={'/system/logs/files'}
|
||||
>
|
||||
{translate('LogFiles')}
|
||||
Log Files
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem
|
||||
to={'/system/logs/files/update'}
|
||||
>
|
||||
{translate('UpdaterLogFiles')}
|
||||
Updater Log Files
|
||||
</MenuItem>
|
||||
</MenuContent>
|
||||
</Menu>
|
||||
|
||||
@@ -45,14 +45,7 @@ class About extends Component {
|
||||
packageVersion &&
|
||||
<DescriptionListItem
|
||||
title={translate('PackageVersion')}
|
||||
data={(packageAuthor ?
|
||||
<InlineMarkdown data={translate('PackageVersionInfo', {
|
||||
packageVersion,
|
||||
packageAuthor
|
||||
})}
|
||||
/> :
|
||||
packageVersion
|
||||
)}
|
||||
data={(packageAuthor ? <span> {packageVersion} {' by '} <InlineMarkdown data={packageAuthor} /> </span> : packageVersion)}
|
||||
/>
|
||||
}
|
||||
|
||||
|
||||
@@ -153,7 +153,7 @@ class Health extends Component {
|
||||
{
|
||||
!healthIssues &&
|
||||
<div className={styles.healthOk}>
|
||||
{translate('NoIssuesWithYourConfiguration')}
|
||||
{translate('HealthNoIssues')}
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ class MoreInfo extends Component {
|
||||
{translate('Wiki')}
|
||||
</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
<Link to="https://wiki.servarr.com/radarr">wiki.servarr.com/radarr</Link>
|
||||
<Link to="https://wiki.servarr.com/radarr">{translate('Wiki')}</Link>
|
||||
</DescriptionListItemDescription>
|
||||
|
||||
<DescriptionListItemTitle>
|
||||
|
||||
@@ -44,7 +44,7 @@ class Updates extends Component {
|
||||
const hasUpdateToInstall = hasUpdates && _.some(items, { installable: true, latest: true });
|
||||
const noUpdateToInstall = hasUpdates && !hasUpdateToInstall;
|
||||
|
||||
const externalUpdaterPrefix = translate('UpdateRadarrDirectlyLoadError');
|
||||
const externalUpdaterPrefix = translate('UnableToUpdateRadarrDirectly');
|
||||
const externalUpdaterMessages = {
|
||||
external: translate('ExternalUpdater'),
|
||||
apt: translate('AptUpdater'),
|
||||
@@ -176,7 +176,7 @@ class Updates extends Component {
|
||||
kind={kinds.INVERSE}
|
||||
title={formatDateTime(update.installedOn, longDateFormat, timeFormat)}
|
||||
>
|
||||
{translate('PreviouslyInstalled')}
|
||||
Previously Installed
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
@@ -213,14 +213,14 @@ class Updates extends Component {
|
||||
{
|
||||
!!updatesError &&
|
||||
<div>
|
||||
{translate('FailedToFetchUpdates')}
|
||||
Failed to fetch updates
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
!!generalSettingsError &&
|
||||
<div>
|
||||
{translate('FailedToUpdateSettings')}
|
||||
Failed to update settings
|
||||
</div>
|
||||
}
|
||||
</PageContentBody>
|
||||
|
||||
@@ -25,18 +25,20 @@ export async function fetchTranslations(): Promise<boolean> {
|
||||
|
||||
export default function translate(
|
||||
key: string,
|
||||
tokens: Record<string, string | number | boolean> = {}
|
||||
tokens?: Record<string, string | number | boolean>
|
||||
) {
|
||||
const translation = translations[key] || key;
|
||||
|
||||
tokens.appName = 'Radarr';
|
||||
if (tokens) {
|
||||
// Fallback to the old behaviour for translations not yet updated to use named tokens
|
||||
Object.values(tokens).forEach((value, index) => {
|
||||
tokens[index] = value;
|
||||
});
|
||||
|
||||
// Fallback to the old behaviour for translations not yet updated to use named tokens
|
||||
Object.values(tokens).forEach((value, index) => {
|
||||
tokens[index] = value;
|
||||
});
|
||||
return translation.replace(/\{([a-z0-9]+?)\}/gi, (match, tokenMatch) =>
|
||||
String(tokens[tokenMatch] ?? match)
|
||||
);
|
||||
}
|
||||
|
||||
return translation.replace(/\{([a-z0-9]+?)\}/gi, (match, tokenMatch) =>
|
||||
String(tokens[tokenMatch] ?? match)
|
||||
);
|
||||
return translation;
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ import { render } from 'react-dom';
|
||||
import createAppStore from 'Store/createAppStore';
|
||||
import App from './App/App';
|
||||
|
||||
import 'Diag/ConsoleApi';
|
||||
|
||||
export async function bootstrap() {
|
||||
const history = createBrowserHistory();
|
||||
const store = createAppStore(history);
|
||||
|
||||
@@ -12,10 +12,8 @@ export interface Field {
|
||||
|
||||
interface ImportList extends ModelBase {
|
||||
enable: boolean;
|
||||
enabled: boolean;
|
||||
enableAuto: boolean;
|
||||
qualityProfileId: number;
|
||||
minimumAvailability: string;
|
||||
rootFolderPath: string;
|
||||
name: string;
|
||||
fields: Field[];
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user