1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-04-17 21:26:22 -04:00

Compare commits

..

3 Commits

Author SHA1 Message Date
Qstick
04b15f2178 Revert cover mapping for collections 2023-10-07 14:46:07 -05:00
Qstick
39d7320a75 Test mapping before collections loop 2023-10-07 13:47:46 -05:00
Qstick
207a4b19dc Test Logging 2023-10-07 13:47:46 -05:00
217 changed files with 3681 additions and 5225 deletions

View File

@@ -2,7 +2,7 @@
[![Build Status](https://dev.azure.com/Radarr/Radarr/_apis/build/status/Radarr.Radarr?branchName=develop)](https://dev.azure.com/Radarr/Radarr/_build/latest?definitionId=1&branchName=develop) [![Build Status](https://dev.azure.com/Radarr/Radarr/_apis/build/status/Radarr.Radarr?branchName=develop)](https://dev.azure.com/Radarr/Radarr/_build/latest?definitionId=1&branchName=develop)
[![Translated](https://translate.servarr.com/widgets/servarr/-/radarr/svg-badge.svg)](https://translate.servarr.com/engage/radarr/?utm_source=widget) [![Translated](https://translate.servarr.com/widgets/servarr/-/radarr/svg-badge.svg)](https://translate.servarr.com/engage/radarr/?utm_source=widget)
[![Docker Pulls](https://img.shields.io/docker/pulls/linuxserver/radarr.svg)](https://wiki.servarr.com/radarr/installation/docker) [![Docker Pulls](https://img.shields.io/docker/pulls/linuxserver/radarr.svg)](https://wiki.servarr.com/radarr/installation#docker)
![Github Downloads](https://img.shields.io/github/downloads/Radarr/Radarr/total.svg) ![Github Downloads](https://img.shields.io/github/downloads/Radarr/Radarr/total.svg)
[![Backers on Open Collective](https://opencollective.com/Radarr/backers/badge.svg)](#backers) [![Backers on Open Collective](https://opencollective.com/Radarr/backers/badge.svg)](#backers)
[![Sponsors on Open Collective](https://opencollective.com/Radarr/sponsors/badge.svg)](#sponsors) [![Sponsors on Open Collective](https://opencollective.com/Radarr/sponsors/badge.svg)](#sponsors)

View File

@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests' testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '5.2.1' majorVersion: '5.0.3'
minorVersion: $[counter('minorVersion', 2000)] minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)' radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)' buildName: '$(Build.SourceBranchName).$(radarrVersion)'

View File

@@ -14,7 +14,6 @@ import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptions
import TablePager from 'Components/Table/TablePager'; import TablePager from 'Components/Table/TablePager';
import { align, icons, kinds } from 'Helpers/Props'; import { align, icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import HistoryFilterModal from './HistoryFilterModal';
import HistoryRowConnector from './HistoryRowConnector'; import HistoryRowConnector from './HistoryRowConnector';
class History extends Component { class History extends Component {
@@ -34,7 +33,6 @@ class History extends Component {
columns, columns,
selectedFilterKey, selectedFilterKey,
filters, filters,
customFilters,
totalRecords, totalRecords,
onFilterSelect, onFilterSelect,
onFirstPagePress, onFirstPagePress,
@@ -72,8 +70,7 @@ class History extends Component {
alignMenu={align.RIGHT} alignMenu={align.RIGHT}
selectedFilterKey={selectedFilterKey} selectedFilterKey={selectedFilterKey}
filters={filters} filters={filters}
customFilters={customFilters} customFilters={[]}
filterModalConnectorComponent={HistoryFilterModal}
onFilterSelect={onFilterSelect} onFilterSelect={onFilterSelect}
/> />
</PageToolbarSection> </PageToolbarSection>
@@ -147,9 +144,8 @@ History.propTypes = {
moviesError: PropTypes.object, moviesError: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired,
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, selectedFilterKey: PropTypes.string.isRequired,
filters: PropTypes.arrayOf(PropTypes.object).isRequired, filters: PropTypes.arrayOf(PropTypes.object).isRequired,
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
totalRecords: PropTypes.number, totalRecords: PropTypes.number,
onFilterSelect: PropTypes.func.isRequired, onFilterSelect: PropTypes.func.isRequired,
onFirstPagePress: PropTypes.func.isRequired onFirstPagePress: PropTypes.func.isRequired

View File

@@ -4,7 +4,6 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import withCurrentPage from 'Components/withCurrentPage'; import withCurrentPage from 'Components/withCurrentPage';
import * as historyActions from 'Store/Actions/historyActions'; import * as historyActions from 'Store/Actions/historyActions';
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator'; import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
import History from './History'; import History from './History';
@@ -12,13 +11,11 @@ function createMapStateToProps() {
return createSelector( return createSelector(
(state) => state.history, (state) => state.history,
(state) => state.movies, (state) => state.movies,
createCustomFiltersSelector('history'), (history, movies) => {
(history, movies, customFilters) => {
return { return {
isMoviesFetching: movies.isFetching, isMoviesFetching: movies.isFetching,
isMoviesPopulated: movies.isPopulated, isMoviesPopulated: movies.isPopulated,
moviesError: movies.error, moviesError: movies.error,
customFilters,
...history ...history
}; };
} }

View File

@@ -1,54 +0,0 @@
import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import FilterModal from 'Components/Filter/FilterModal';
import { setHistoryFilter } from 'Store/Actions/historyActions';
function createHistorySelector() {
return createSelector(
(state: AppState) => state.history.items,
(queueItems) => {
return queueItems;
}
);
}
function createFilterBuilderPropsSelector() {
return createSelector(
(state: AppState) => state.history.filterBuilderProps,
(filterBuilderProps) => {
return filterBuilderProps;
}
);
}
interface HistoryFilterModalProps {
isOpen: boolean;
}
export default function HistoryFilterModal(props: HistoryFilterModalProps) {
const sectionItems = useSelector(createHistorySelector());
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
const customFilterType = 'history';
const dispatch = useDispatch();
const dispatchSetFilter = useCallback(
(payload: unknown) => {
dispatch(setHistoryFilter(payload));
},
[dispatch]
);
return (
<FilterModal
// TODO: Don't spread all the props
{...props}
sectionItems={sectionItems}
filterBuilderProps={filterBuilderProps}
customFilterType={customFilterType}
dispatchSetFilter={dispatchSetFilter}
/>
);
}

View File

@@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Alert from 'Components/Alert'; import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import FilterMenu from 'Components/Menu/FilterMenu';
import PageContent from 'Components/Page/PageContent'; import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody'; import PageContentBody from 'Components/Page/PageContentBody';
import PageToolbar from 'Components/Page/Toolbar/PageToolbar'; import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
@@ -22,7 +21,6 @@ import getSelectedIds from 'Utilities/Table/getSelectedIds';
import removeOldSelectedState from 'Utilities/Table/removeOldSelectedState'; import removeOldSelectedState from 'Utilities/Table/removeOldSelectedState';
import selectAll from 'Utilities/Table/selectAll'; import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected'; import toggleSelected from 'Utilities/Table/toggleSelected';
import QueueFilterModal from './QueueFilterModal';
import QueueOptionsConnector from './QueueOptionsConnector'; import QueueOptionsConnector from './QueueOptionsConnector';
import QueueRowConnector from './QueueRowConnector'; import QueueRowConnector from './QueueRowConnector';
import RemoveQueueItemsModal from './RemoveQueueItemsModal'; import RemoveQueueItemsModal from './RemoveQueueItemsModal';
@@ -155,16 +153,11 @@ class Queue extends Component {
isMoviesPopulated, isMoviesPopulated,
moviesError, moviesError,
columns, columns,
selectedFilterKey,
filters,
customFilters,
count,
totalRecords, totalRecords,
isGrabbing, isGrabbing,
isRemoving, isRemoving,
isRefreshMonitoredDownloadsExecuting, isRefreshMonitoredDownloadsExecuting,
onRefreshPress, onRefreshPress,
onFilterSelect,
...otherProps ...otherProps
} = this.props; } = this.props;
@@ -227,15 +220,6 @@ class Queue extends Component {
iconName={icons.TABLE} iconName={icons.TABLE}
/> />
</TableOptionsModalWrapper> </TableOptionsModalWrapper>
<FilterMenu
alignMenu={align.RIGHT}
selectedFilterKey={selectedFilterKey}
filters={filters}
customFilters={customFilters}
filterModalConnectorComponent={QueueFilterModal}
onFilterSelect={onFilterSelect}
/>
</PageToolbarSection> </PageToolbarSection>
</PageToolbar> </PageToolbar>
@@ -257,11 +241,7 @@ class Queue extends Component {
{ {
isAllPopulated && !hasError && !items.length ? isAllPopulated && !hasError && !items.length ?
<Alert kind={kinds.INFO}> <Alert kind={kinds.INFO}>
{ {translate('QueueIsEmpty')}
selectedFilterKey !== 'all' && count > 0 ?
translate('QueueFilterHasNoItems') :
translate('QueueIsEmpty')
}
</Alert> : </Alert> :
null null
} }
@@ -345,22 +325,13 @@ Queue.propTypes = {
moviesError: PropTypes.object, moviesError: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired, columns: 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,
count: PropTypes.number.isRequired,
totalRecords: PropTypes.number, totalRecords: PropTypes.number,
isGrabbing: PropTypes.bool.isRequired, isGrabbing: PropTypes.bool.isRequired,
isRemoving: PropTypes.bool.isRequired, isRemoving: PropTypes.bool.isRequired,
isRefreshMonitoredDownloadsExecuting: PropTypes.bool.isRequired, isRefreshMonitoredDownloadsExecuting: PropTypes.bool.isRequired,
onRefreshPress: PropTypes.func.isRequired, onRefreshPress: PropTypes.func.isRequired,
onGrabSelectedPress: PropTypes.func.isRequired, onGrabSelectedPress: PropTypes.func.isRequired,
onRemoveSelectedPress: PropTypes.func.isRequired, onRemoveSelectedPress: PropTypes.func.isRequired
onFilterSelect: PropTypes.func.isRequired
};
Queue.defaultProps = {
count: 0
}; };
export default Queue; export default Queue;

View File

@@ -6,7 +6,6 @@ import * as commandNames from 'Commands/commandNames';
import withCurrentPage from 'Components/withCurrentPage'; import withCurrentPage from 'Components/withCurrentPage';
import { executeCommand } from 'Store/Actions/commandActions'; import { executeCommand } from 'Store/Actions/commandActions';
import * as queueActions from 'Store/Actions/queueActions'; import * as queueActions from 'Store/Actions/queueActions';
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator'; import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
import Queue from './Queue'; import Queue from './Queue';
@@ -16,16 +15,12 @@ function createMapStateToProps() {
(state) => state.movies, (state) => state.movies,
(state) => state.queue.options, (state) => state.queue.options,
(state) => state.queue.paged, (state) => state.queue.paged,
(state) => state.queue.status.item,
createCustomFiltersSelector('queue'),
createCommandExecutingSelector(commandNames.REFRESH_MONITORED_DOWNLOADS), createCommandExecutingSelector(commandNames.REFRESH_MONITORED_DOWNLOADS),
(movies, options, queue, status, customFilters, isRefreshMonitoredDownloadsExecuting) => { (movies, options, queue, isRefreshMonitoredDownloadsExecuting) => {
return { return {
count: options.includeUnknownMovieItems ? status.totalCount : status.count,
isMoviesFetching: movies.isFetching, isMoviesFetching: movies.isFetching,
isMoviesPopulated: movies.isPopulated, isMoviesPopulated: movies.isPopulated,
moviesError: movies.error, moviesError: movies.error,
customFilters,
isRefreshMonitoredDownloadsExecuting, isRefreshMonitoredDownloadsExecuting,
...options, ...options,
...queue ...queue
@@ -111,10 +106,6 @@ class QueueConnector extends Component {
this.props.setQueueSort({ sortKey }); this.props.setQueueSort({ sortKey });
}; };
onFilterSelect = (selectedFilterKey) => {
this.props.setQueueFilter({ selectedFilterKey });
};
onTableOptionChange = (payload) => { onTableOptionChange = (payload) => {
this.props.setQueueTableOption(payload); this.props.setQueueTableOption(payload);
@@ -149,7 +140,6 @@ class QueueConnector extends Component {
onLastPagePress={this.onLastPagePress} onLastPagePress={this.onLastPagePress}
onPageSelect={this.onPageSelect} onPageSelect={this.onPageSelect}
onSortPress={this.onSortPress} onSortPress={this.onSortPress}
onFilterSelect={this.onFilterSelect}
onTableOptionChange={this.onTableOptionChange} onTableOptionChange={this.onTableOptionChange}
onRefreshPress={this.onRefreshPress} onRefreshPress={this.onRefreshPress}
onGrabSelectedPress={this.onGrabSelectedPress} onGrabSelectedPress={this.onGrabSelectedPress}
@@ -172,7 +162,6 @@ QueueConnector.propTypes = {
gotoQueueLastPage: PropTypes.func.isRequired, gotoQueueLastPage: PropTypes.func.isRequired,
gotoQueuePage: PropTypes.func.isRequired, gotoQueuePage: PropTypes.func.isRequired,
setQueueSort: PropTypes.func.isRequired, setQueueSort: PropTypes.func.isRequired,
setQueueFilter: PropTypes.func.isRequired,
setQueueTableOption: PropTypes.func.isRequired, setQueueTableOption: PropTypes.func.isRequired,
clearQueue: PropTypes.func.isRequired, clearQueue: PropTypes.func.isRequired,
grabQueueItems: PropTypes.func.isRequired, grabQueueItems: PropTypes.func.isRequired,

View File

@@ -81,9 +81,4 @@ QueueDetails.propTypes = {
progressBar: PropTypes.node.isRequired progressBar: PropTypes.node.isRequired
}; };
QueueDetails.defaultProps = {
trackedDownloadStatus: 'ok',
trackedDownloadState: 'downloading'
};
export default QueueDetails; export default QueueDetails;

View File

@@ -1,54 +0,0 @@
import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import FilterModal from 'Components/Filter/FilterModal';
import { setQueueFilter } from 'Store/Actions/queueActions';
function createQueueSelector() {
return createSelector(
(state: AppState) => state.queue.paged.items,
(queueItems) => {
return queueItems;
}
);
}
function createFilterBuilderPropsSelector() {
return createSelector(
(state: AppState) => state.queue.paged.filterBuilderProps,
(filterBuilderProps) => {
return filterBuilderProps;
}
);
}
interface QueueFilterModalProps {
isOpen: boolean;
}
export default function QueueFilterModal(props: QueueFilterModalProps) {
const sectionItems = useSelector(createQueueSelector());
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
const customFilterType = 'queue';
const dispatch = useDispatch();
const dispatchSetFilter = useCallback(
(payload: unknown) => {
dispatch(setQueueFilter(payload));
},
[dispatch]
);
return (
<FilterModal
// TODO: Don't spread all the props
{...props}
sectionItems={sectionItems}
filterBuilderProps={filterBuilderProps}
customFilterType={customFilterType}
dispatchSetFilter={dispatchSetFilter}
/>
);
}

View File

@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRowCell from 'Components/Table/Cells/TableRowCell';
import { tooltipPositions } from 'Helpers/Props'; import { tooltipPositions } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import QueueStatus from './QueueStatus'; import QueueStatus from './QueueStatus';
import styles from './QueueStatusCell.css'; import styles from './QueueStatusCell.css';
@@ -40,8 +41,8 @@ QueueStatusCell.propTypes = {
}; };
QueueStatusCell.defaultProps = { QueueStatusCell.defaultProps = {
trackedDownloadStatus: 'ok', trackedDownloadStatus: translate('Ok'),
trackedDownloadState: 'downloading' trackedDownloadState: translate('Downloading')
}; };
export default QueueStatusCell; export default QueueStatusCell;

View File

@@ -1,9 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import Icon from 'Components/Icon';
import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRowCell from 'Components/Table/Cells/TableRowCell';
import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import formatTime from 'Utilities/Date/formatTime'; import formatTime from 'Utilities/Date/formatTime';
import formatTimeSpan from 'Utilities/Date/formatTimeSpan'; import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
import getRelativeDate from 'Utilities/Date/getRelativeDate'; import getRelativeDate from 'Utilities/Date/getRelativeDate';
@@ -28,13 +25,11 @@ function TimeleftCell(props) {
const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true }); const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true });
return ( return (
<TableRowCell className={styles.timeleft}> <TableRowCell
<Tooltip className={styles.timeleft}
anchor={<Icon name={icons.INFO} />} title={translate('DelayingDownloadUntil', { date, time })}
tooltip={translate('DelayingDownloadUntil', { date, time })} >
kind={kinds.INVERSE} -
position={tooltipPositions.TOP}
/>
</TableRowCell> </TableRowCell>
); );
} }
@@ -44,13 +39,11 @@ function TimeleftCell(props) {
const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true }); const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true });
return ( return (
<TableRowCell className={styles.timeleft}> <TableRowCell
<Tooltip className={styles.timeleft}
anchor={<Icon name={icons.INFO} />} title={translate('RetryingDownloadOn', { date, time })}
tooltip={translate('RetryingDownloadOn', { date, time })} >
kind={kinds.INVERSE} -
position={tooltipPositions.TOP}
/>
</TableRowCell> </TableRowCell>
); );
} }

View File

@@ -1,6 +1,5 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Alert from 'Components/Alert';
import TextInput from 'Components/Form/TextInput'; import TextInput from 'Components/Form/TextInput';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import Button from 'Components/Link/Button'; import Button from 'Components/Link/Button';
@@ -131,12 +130,7 @@ class AddNewMovie extends Component {
<div className={styles.helpText}> <div className={styles.helpText}>
{translate('FailedLoadingSearchResults')} {translate('FailedLoadingSearchResults')}
</div> </div>
<Alert kind={kinds.WARNING}>{getErrorMessage(error)}</Alert> <div>{getErrorMessage(error)}</div>
<div>
<Link to="https://wiki.servarr.com/radarr/troubleshooting#invalid-response-received-from-tmdb">
{translate('WhySearchesCouldBeFailing')}
</Link>
</div>
</div> : null </div> : null
} }

View File

@@ -66,7 +66,6 @@ class AddNewMovieSearchResult extends Component {
ratings, ratings,
folder, folder,
images, images,
existingMovieId,
isExistingMovie, isExistingMovie,
isExclusionMovie, isExclusionMovie,
isSmallScreen, isSmallScreen,
@@ -75,7 +74,8 @@ class AddNewMovieSearchResult extends Component {
monitored, monitored,
hasFile, hasFile,
isAvailable, isAvailable,
movieFile, queueStatus,
queueState,
runtime, runtime,
movieRuntimeFormat, movieRuntimeFormat,
certification certification
@@ -120,13 +120,13 @@ class AddNewMovieSearchResult extends Component {
{ {
isExistingMovie && isExistingMovie &&
<MovieIndexProgressBar <MovieIndexProgressBar
movieId={existingMovieId}
movieFile={movieFile}
monitored={monitored} monitored={monitored}
hasFile={hasFile} hasFile={hasFile}
status={status} status={status}
width={posterWidth} width={posterWidth}
detailedProgressBar={true} detailedProgressBar={true}
queueStatus={queueStatus}
queueState={queueState}
isAvailable={isAvailable} isAvailable={isAvailable}
/> />
} }
@@ -278,7 +278,6 @@ AddNewMovieSearchResult.propTypes = {
ratings: PropTypes.object.isRequired, ratings: PropTypes.object.isRequired,
folder: PropTypes.string.isRequired, folder: PropTypes.string.isRequired,
images: PropTypes.arrayOf(PropTypes.object).isRequired, images: PropTypes.arrayOf(PropTypes.object).isRequired,
existingMovieId: PropTypes.number,
isExistingMovie: PropTypes.bool.isRequired, isExistingMovie: PropTypes.bool.isRequired,
isExclusionMovie: PropTypes.bool.isRequired, isExclusionMovie: PropTypes.bool.isRequired,
isSmallScreen: PropTypes.bool.isRequired, isSmallScreen: PropTypes.bool.isRequired,
@@ -287,8 +286,9 @@ AddNewMovieSearchResult.propTypes = {
monitored: PropTypes.bool.isRequired, monitored: PropTypes.bool.isRequired,
hasFile: PropTypes.bool.isRequired, hasFile: PropTypes.bool.isRequired,
isAvailable: PropTypes.bool.isRequired, isAvailable: PropTypes.bool.isRequired,
movieFile: PropTypes.object,
colorImpairedMode: PropTypes.bool, colorImpairedMode: PropTypes.bool,
queueStatus: PropTypes.string,
queueState: PropTypes.string,
runtime: PropTypes.number.isRequired, runtime: PropTypes.number.isRequired,
movieRuntimeFormat: PropTypes.string.isRequired, movieRuntimeFormat: PropTypes.string.isRequired,
certification: PropTypes.string certification: PropTypes.string

View File

@@ -10,15 +10,17 @@ function createMapStateToProps() {
createExistingMovieSelector(), createExistingMovieSelector(),
createExclusionMovieSelector(), createExclusionMovieSelector(),
createDimensionsSelector(), createDimensionsSelector(),
(state) => state.queue.details.items,
(state, { internalId }) => internalId, (state, { internalId }) => internalId,
(state) => state.settings.ui.item.movieRuntimeFormat, (isExistingMovie, isExclusionMovie, dimensions, queueItems, internalId) => {
(isExistingMovie, isExclusionMovie, dimensions, internalId, movieRuntimeFormat) => { const firstQueueItem = queueItems.find((q) => q.movieId === internalId && internalId > 0);
return { return {
existingMovieId: internalId,
isExistingMovie, isExistingMovie,
isExclusionMovie, isExclusionMovie,
isSmallScreen: dimensions.isSmallScreen, isSmallScreen: dimensions.isSmallScreen,
movieRuntimeFormat queueStatus: firstQueueItem ? firstQueueItem.status : null,
queueState: firstQueueItem ? firstQueueItem.trackedDownloadState : null
}; };
} }
); );

View File

@@ -5,13 +5,12 @@ import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { addRootFolder, deleteRootFolder, fetchRootFolders } from 'Store/Actions/rootFolderActions'; import { addRootFolder, deleteRootFolder, fetchRootFolders } from 'Store/Actions/rootFolderActions';
import createRootFoldersSelector from 'Store/Selectors/createRootFoldersSelector';
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector'; import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
import ImportMovieSelectFolder from './ImportMovieSelectFolder'; import ImportMovieSelectFolder from './ImportMovieSelectFolder';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
createRootFoldersSelector(), (state) => state.rootFolders,
createSystemStatusSelector(), createSystemStatusSelector(),
(rootFolders, systemStatus) => { (rootFolders, systemStatus) => {
return { return {

View File

@@ -1,5 +1,4 @@
import SortDirection from 'Helpers/Props/SortDirection'; import SortDirection from 'Helpers/Props/SortDirection';
import { FilterBuilderProp } from './AppState';
export interface Error { export interface Error {
responseJSON: { responseJSON: {
@@ -21,10 +20,6 @@ export interface PagedAppSectionState {
pageSize: number; pageSize: number;
} }
export interface AppSectionFilterState<T> {
filterBuilderProps: FilterBuilderProp<T>[];
}
export interface AppSectionSchemaState<T> { export interface AppSectionSchemaState<T> {
isSchemaFetching: boolean; isSchemaFetching: boolean;
isSchemaPopulated: boolean; isSchemaPopulated: boolean;

View File

@@ -1,7 +1,6 @@
import InteractiveImportAppState from 'App/State/InteractiveImportAppState'; import InteractiveImportAppState from 'App/State/InteractiveImportAppState';
import CalendarAppState from './CalendarAppState'; import CalendarAppState from './CalendarAppState';
import CommandAppState from './CommandAppState'; import CommandAppState from './CommandAppState';
import HistoryAppState from './HistoryAppState';
import MovieCollectionAppState from './MovieCollectionAppState'; import MovieCollectionAppState from './MovieCollectionAppState';
import MovieFilesAppState from './MovieFilesAppState'; import MovieFilesAppState from './MovieFilesAppState';
import MoviesAppState, { MovieIndexAppState } from './MoviesAppState'; import MoviesAppState, { MovieIndexAppState } from './MoviesAppState';
@@ -47,7 +46,6 @@ export interface CustomFilter {
interface AppState { interface AppState {
calendar: CalendarAppState; calendar: CalendarAppState;
commands: CommandAppState; commands: CommandAppState;
history: HistoryAppState;
interactiveImport: InteractiveImportAppState; interactiveImport: InteractiveImportAppState;
movieCollections: MovieCollectionAppState; movieCollections: MovieCollectionAppState;
movieFiles: MovieFilesAppState; movieFiles: MovieFilesAppState;

View File

@@ -1,10 +1,9 @@
import AppSectionState, { import AppSectionState from 'App/State/AppSectionState';
AppSectionFilterState,
} from 'App/State/AppSectionState';
import Movie from 'Movie/Movie'; import Movie from 'Movie/Movie';
import { FilterBuilderProp } from './AppState';
interface CalendarAppState interface CalendarAppState extends AppSectionState<Movie> {
extends AppSectionState<Movie>, filterBuilderProps: FilterBuilderProp<Movie>[];
AppSectionFilterState<Movie> {} }
export default CalendarAppState; export default CalendarAppState;

View File

@@ -1,10 +0,0 @@
import AppSectionState, {
AppSectionFilterState,
} from 'App/State/AppSectionState';
import History from 'typings/History';
interface HistoryAppState
extends AppSectionState<History>,
AppSectionFilterState<History> {}
export default HistoryAppState;

View File

@@ -1,8 +1,6 @@
import AppSectionState from 'App/State/AppSectionState'; import AppSectionState from 'App/State/AppSectionState';
import MovieCollection from 'typings/MovieCollection'; import MovieCollection from 'typings/MovieCollection';
interface MovieCollectionAppState extends AppSectionState<MovieCollection> { type MovieCollectionAppState = AppSectionState<MovieCollection>;
itemMap: Record<number, number>;
}
export default MovieCollectionAppState; export default MovieCollectionAppState;

View File

@@ -1,17 +1,41 @@
import Queue from 'typings/Queue'; import ModelBase from 'App/ModelBase';
import AppSectionState, { import Language from 'Language/Language';
AppSectionFilterState, import { QualityModel } from 'Quality/Quality';
AppSectionItemState, import CustomFormat from 'typings/CustomFormat';
Error, import AppSectionState, { AppSectionItemState, Error } from './AppSectionState';
} from './AppSectionState';
export interface StatusMessage {
title: string;
messages: string[];
}
export interface Queue extends ModelBase {
languages: Language[];
quality: QualityModel;
customFormats: CustomFormat[];
size: number;
title: string;
sizeleft: number;
timeleft: string;
estimatedCompletionTime: string;
status: string;
trackedDownloadStatus: string;
trackedDownloadState: string;
statusMessages: StatusMessage[];
errorMessage: string;
downloadId: string;
protocol: string;
downloadClient: string;
outputPath: string;
movieHasFile: boolean;
movieId?: number;
}
export interface QueueDetailsAppState extends AppSectionState<Queue> { export interface QueueDetailsAppState extends AppSectionState<Queue> {
params: unknown; params: unknown;
} }
export interface QueuePagedAppState export interface QueuePagedAppState extends AppSectionState<Queue> {
extends AppSectionState<Queue>,
AppSectionFilterState<Queue> {
isGrabbing: boolean; isGrabbing: boolean;
grabError: Error; grabError: Error;
isRemoving: boolean; isRemoving: boolean;

View File

@@ -42,9 +42,9 @@ function Agenda(props) {
<div className={styles.agenda}> <div className={styles.agenda}>
{ {
items.map((item, index) => { items.map((item, index) => {
const momentDate = moment(item.sortDate); const momentDate = moment(item.inCinemas);
const showDate = index === 0 || const showDate = index === 0 ||
!moment(items[index - 1].sortDate).isSame(momentDate, 'day'); !moment(items[index - 1].inCinemas).isSame(momentDate, 'day');
return ( return (
<AgendaEventConnector <AgendaEventConnector

View File

@@ -88,7 +88,7 @@
} }
@media only screen and (max-width: $breakpointSmall) { @media only screen and (max-width: $breakpointSmall) {
.overlay { .event {
flex-direction: column; flex-direction: column;
} }
@@ -111,4 +111,5 @@
.releaseIcon { .releaseIcon {
margin-right: 20px; margin-right: 20px;
width: 25px; width: 25px;
text-align: right;
} }

View File

@@ -95,7 +95,7 @@ class AgendaEvent extends Component {
<div className={styles.overlay}> <div className={styles.overlay}>
<div className={styles.date}> <div className={styles.date}>
{showDate ? startTime.format(longDateFormat) : null} {(showDate) ? startTime.format(longDateFormat) : null}
</div> </div>
<div className={styles.releaseIcon}> <div className={styles.releaseIcon}>

View File

@@ -23,11 +23,13 @@ function createFilterBuilderPropsSelector() {
); );
} }
interface CalendarFilterModalProps { interface SeriesIndexFilterModalProps {
isOpen: boolean; isOpen: boolean;
} }
export default function CalendarFilterModal(props: CalendarFilterModalProps) { export default function CalendarFilterModal(
props: SeriesIndexFilterModalProps
) {
const sectionItems = useSelector(createCalendarSelector()); const sectionItems = useSelector(createCalendarSelector());
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector()); const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
const customFilterType = 'calendar'; const customFilterType = 'calendar';

View File

@@ -21,7 +21,6 @@ function createMapStateToProps() {
return { return {
...collection, ...collection,
movies: [...collection.movies].sort((a, b) => b.year - a.year),
genres: Array.from(new Set(allGenres)).slice(0, 3) genres: Array.from(new Set(allGenres)).slice(0, 3)
}; };
} }

View File

@@ -61,7 +61,6 @@ class CollectionMovie extends Component {
const { const {
id, id,
title, title,
status,
overview, overview,
year, year,
tmdbId, tmdbId,
@@ -124,11 +123,11 @@ class CollectionMovie extends Component {
<div className={styles.overlay}> <div className={styles.overlay}>
<div className={styles.overlayTitle}> <div className={styles.overlayTitle}>
{title} {year > 0 ? `(${year})` : ''} {title}
</div> </div>
{ {
id ? id &&
<div className={styles.overlayStatus}> <div className={styles.overlayStatus}>
<MovieIndexProgressBar <MovieIndexProgressBar
monitored={monitored} monitored={monitored}
@@ -139,8 +138,7 @@ class CollectionMovie extends Component {
detailedProgressBar={detailedProgressBar} detailedProgressBar={detailedProgressBar}
isAvailable={isAvailable} isAvailable={isAvailable}
/> />
</div> : </div>
null
} }
</div> </div>
</Link> </Link>
@@ -173,7 +171,6 @@ CollectionMovie.propTypes = {
id: PropTypes.number, id: PropTypes.number,
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
year: PropTypes.number.isRequired, year: PropTypes.number.isRequired,
status: PropTypes.string.isRequired,
overview: PropTypes.string.isRequired, overview: PropTypes.string.isRequired,
monitored: PropTypes.bool, monitored: PropTypes.bool,
collectionId: PropTypes.number.isRequired, collectionId: PropTypes.number.isRequired,

View File

@@ -5,7 +5,7 @@
margin: 2px 4px; margin: 2px 4px;
border: 1px solid var(--borderColor); border: 1px solid var(--borderColor);
border-radius: 4px; border-radius: 4px;
background-color: var(--inputBackgroundColor); background-color: #eee;
cursor: default; cursor: default;
} }
@@ -17,7 +17,7 @@
padding: 0 4px; padding: 0 4px;
border-left: 4px; border-left: 4px;
border-left-style: solid; border-left-style: solid;
background-color: var(--themeLightColor); background-color: var(--white);
color: var(--defaultColor); color: var(--defaultColor);
} }

View File

@@ -14,7 +14,6 @@ class CollectionMovieLabel extends Component {
const { const {
id, id,
title, title,
year,
status, status,
monitored, monitored,
isAvailable, isAvailable,
@@ -36,7 +35,9 @@ class CollectionMovieLabel extends Component {
} }
<span> <span>
{title} {year > 0 ? `(${year})` : ''} {
title
}
</span> </span>
</div> </div>
@@ -61,7 +62,6 @@ class CollectionMovieLabel extends Component {
CollectionMovieLabel.propTypes = { CollectionMovieLabel.propTypes = {
id: PropTypes.number, id: PropTypes.number,
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
year: PropTypes.number.isRequired,
status: PropTypes.string, status: PropTypes.string,
isAvailable: PropTypes.bool, isAvailable: PropTypes.bool,
monitored: PropTypes.bool, monitored: PropTypes.bool,

View File

@@ -28,6 +28,7 @@ function calculatePosterWidth(posterSize, isSmallScreen) {
} }
function calculateRowHeight(posterHeight, sortKey, isSmallScreen, overviewOptions) { function calculateRowHeight(posterHeight, sortKey, isSmallScreen, overviewOptions) {
const heights = [ const heights = [
overviewOptions.showPosters ? posterHeight : 75, overviewOptions.showPosters ? posterHeight : 75,
isSmallScreen ? columnPaddingSmallScreen : columnPadding isSmallScreen ? columnPaddingSmallScreen : columnPadding
@@ -121,8 +122,8 @@ class CollectionOverviews extends Component {
overviewOptions overviewOptions
} = this.props; } = this.props;
const posterWidth = overviewOptions.showPosters ? calculatePosterWidth(overviewOptions.size, isSmallScreen) : 0; const posterWidth = calculatePosterWidth(overviewOptions.size, isSmallScreen);
const posterHeight = overviewOptions.showPosters ? calculatePosterHeight(posterWidth) : 0; const posterHeight = calculatePosterHeight(posterWidth);
const rowHeight = calculateRowHeight(posterHeight, sortKey, isSmallScreen, overviewOptions); const rowHeight = calculateRowHeight(posterHeight, sortKey, isSmallScreen, overviewOptions);
this.setState({ this.setState({

View File

@@ -6,12 +6,9 @@ import { filterBuilderTypes, filterBuilderValueTypes, icons } from 'Helpers/Prop
import BoolFilterBuilderRowValue from './BoolFilterBuilderRowValue'; import BoolFilterBuilderRowValue from './BoolFilterBuilderRowValue';
import DateFilterBuilderRowValue from './DateFilterBuilderRowValue'; import DateFilterBuilderRowValue from './DateFilterBuilderRowValue';
import FilterBuilderRowValueConnector from './FilterBuilderRowValueConnector'; import FilterBuilderRowValueConnector from './FilterBuilderRowValueConnector';
import HistoryEventTypeFilterBuilderRowValue from './HistoryEventTypeFilterBuilderRowValue';
import ImportListFilterBuilderRowValueConnector from './ImportListFilterBuilderRowValueConnector'; import ImportListFilterBuilderRowValueConnector from './ImportListFilterBuilderRowValueConnector';
import IndexerFilterBuilderRowValueConnector from './IndexerFilterBuilderRowValueConnector'; import IndexerFilterBuilderRowValueConnector from './IndexerFilterBuilderRowValueConnector';
import LanguageFilterBuilderRowValue from './LanguageFilterBuilderRowValue';
import MinimumAvailabilityFilterBuilderRowValue from './MinimumAvailabilityFilterBuilderRowValue'; import MinimumAvailabilityFilterBuilderRowValue from './MinimumAvailabilityFilterBuilderRowValue';
import MovieFilterBuilderRowValue from './MovieFilterBuilderRowValue';
import ProtocolFilterBuilderRowValue from './ProtocolFilterBuilderRowValue'; import ProtocolFilterBuilderRowValue from './ProtocolFilterBuilderRowValue';
import QualityFilterBuilderRowValueConnector from './QualityFilterBuilderRowValueConnector'; import QualityFilterBuilderRowValueConnector from './QualityFilterBuilderRowValueConnector';
import QualityProfileFilterBuilderRowValueConnector from './QualityProfileFilterBuilderRowValueConnector'; import QualityProfileFilterBuilderRowValueConnector from './QualityProfileFilterBuilderRowValueConnector';
@@ -61,15 +58,9 @@ function getRowValueConnector(selectedFilterBuilderProp) {
case filterBuilderValueTypes.DATE: case filterBuilderValueTypes.DATE:
return DateFilterBuilderRowValue; return DateFilterBuilderRowValue;
case filterBuilderValueTypes.HISTORY_EVENT_TYPE:
return HistoryEventTypeFilterBuilderRowValue;
case filterBuilderValueTypes.INDEXER: case filterBuilderValueTypes.INDEXER:
return IndexerFilterBuilderRowValueConnector; return IndexerFilterBuilderRowValueConnector;
case filterBuilderValueTypes.LANGUAGE:
return LanguageFilterBuilderRowValue;
case filterBuilderValueTypes.PROTOCOL: case filterBuilderValueTypes.PROTOCOL:
return ProtocolFilterBuilderRowValue; return ProtocolFilterBuilderRowValue;
@@ -79,9 +70,6 @@ function getRowValueConnector(selectedFilterBuilderProp) {
case filterBuilderValueTypes.QUALITY_PROFILE: case filterBuilderValueTypes.QUALITY_PROFILE:
return QualityProfileFilterBuilderRowValueConnector; return QualityProfileFilterBuilderRowValueConnector;
case filterBuilderValueTypes.MOVIE:
return MovieFilterBuilderRowValue;
case filterBuilderValueTypes.RELEASE_STATUS: case filterBuilderValueTypes.RELEASE_STATUS:
return ReleaseStatusFilterBuilderRowValue; return ReleaseStatusFilterBuilderRowValue;

View File

@@ -1,16 +0,0 @@
import { FilterBuilderProp } from 'App/State/AppState';
interface FilterBuilderRowOnChangeProps {
name: string;
value: unknown[];
}
interface FilterBuilderRowValueProps {
filterType?: string;
filterValue: string | number | object | string[] | number[] | object[];
selectedFilterBuilderProp: FilterBuilderProp<unknown>;
sectionItem: unknown[];
onChange: (payload: FilterBuilderRowOnChangeProps) => void;
}
export default FilterBuilderRowValueProps;

View File

@@ -1,5 +1,5 @@
.tag { .tag {
display: flex; height: 21px;
&.isLastTag { &.isLastTag {
.or { .or {
@@ -18,5 +18,4 @@
.or { .or {
margin: 0 3px; margin: 0 3px;
color: var(--themeDarkColor); color: var(--themeDarkColor);
line-height: 31px;
} }

View File

@@ -7,7 +7,7 @@ import styles from './FilterBuilderRowValueTag.css';
function FilterBuilderRowValueTag(props) { function FilterBuilderRowValueTag(props) {
return ( return (
<div <span
className={styles.tag} className={styles.tag}
> >
<TagInputTag <TagInputTag
@@ -22,7 +22,7 @@ function FilterBuilderRowValueTag(props) {
{translate('Or')} {translate('Or')}
</div> </div>
} }
</div> </span>
); );
} }

View File

@@ -1,51 +0,0 @@
import React from 'react';
import translate from 'Utilities/String/translate';
import FilterBuilderRowValue from './FilterBuilderRowValue';
import FilterBuilderRowValueProps from './FilterBuilderRowValueProps';
const EVENT_TYPE_OPTIONS = [
{
id: 1,
get name() {
return translate('Grabbed');
},
},
{
id: 3,
get name() {
return translate('Imported');
},
},
{
id: 4,
get name() {
return translate('Failed');
},
},
{
id: 6,
get name() {
return translate('Deleted');
},
},
{
id: 8,
get name() {
return translate('Renamed');
},
},
{
id: 9,
get name() {
return translate('Ignored');
},
},
];
function HistoryEventTypeFilterBuilderRowValue(
props: FilterBuilderRowValueProps
) {
return <FilterBuilderRowValue {...props} tagList={EVENT_TYPE_OPTIONS} />;
}
export default HistoryEventTypeFilterBuilderRowValue;

View File

@@ -1,13 +0,0 @@
import React from 'react';
import { useSelector } from 'react-redux';
import createLanguagesSelector from 'Store/Selectors/createLanguagesSelector';
import FilterBuilderRowValue from './FilterBuilderRowValue';
import FilterBuilderRowValueProps from './FilterBuilderRowValueProps';
function LanguageFilterBuilderRowValue(props: FilterBuilderRowValueProps) {
const { items } = useSelector(createLanguagesSelector());
return <FilterBuilderRowValue {...props} tagList={items} />;
}
export default LanguageFilterBuilderRowValue;

View File

@@ -1,19 +0,0 @@
import React from 'react';
import { useSelector } from 'react-redux';
import Movie from 'Movie/Movie';
import createAllMoviesSelector from 'Store/Selectors/createAllMoviesSelector';
import sortByName from 'Utilities/Array/sortByName';
import FilterBuilderRowValue from './FilterBuilderRowValue';
import FilterBuilderRowValueProps from './FilterBuilderRowValueProps';
function MovieFilterBuilderRowValue(props: FilterBuilderRowValueProps) {
const allMovies: Movie[] = useSelector(createAllMoviesSelector());
const tagList = allMovies
.map((movie) => ({ id: movie.id, name: movie.title }))
.sort(sortByName);
return <FilterBuilderRowValue {...props} tagList={tagList} />;
}
export default MovieFilterBuilderRowValue;

View File

@@ -2,10 +2,8 @@
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
margin-right: $formLabelRightMarginWidth; margin-right: $formLabelRightMarginWidth;
padding-top: 8px;
min-height: 35px;
text-align: end;
font-weight: bold; font-weight: bold;
line-height: 35px;
} }
.hasError { .hasError {

View File

@@ -37,8 +37,6 @@ function getType({ type, selectOptionsProviderAction }) {
return inputTypes.OAUTH; return inputTypes.OAUTH;
case 'rootFolder': case 'rootFolder':
return inputTypes.ROOT_FOLDER_SELECT; return inputTypes.ROOT_FOLDER_SELECT;
case 'qualityProfile':
return inputTypes.QUALITY_PROFILE_SELECT;
default: default:
return inputTypes.TEXT; return inputTypes.TEXT;
} }

View File

@@ -3,7 +3,6 @@ import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { addRootFolder } from 'Store/Actions/rootFolderActions'; import { addRootFolder } from 'Store/Actions/rootFolderActions';
import createRootFoldersSelector from 'Store/Selectors/createRootFoldersSelector';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import RootFolderSelectInput from './RootFolderSelectInput'; import RootFolderSelectInput from './RootFolderSelectInput';
@@ -11,7 +10,7 @@ const ADD_NEW_KEY = 'addNew';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
createRootFoldersSelector(), (state) => state.rootFolders,
(state, { value }) => value, (state, { value }) => value,
(state, { includeMissingValue }) => includeMissingValue, (state, { includeMissingValue }) => includeMissingValue,
(state, { includeNoChange }) => includeNoChange, (state, { includeNoChange }) => includeNoChange,

View File

@@ -3,7 +3,6 @@ import React from 'react';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import Link from 'Components/Link/Link'; import Link from 'Components/Link/Link';
import { icons } from 'Helpers/Props'; import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './ModalContent.css'; import styles from './ModalContent.css';
function ModalContent(props) { function ModalContent(props) {
@@ -29,7 +28,6 @@ function ModalContent(props) {
<Icon <Icon
name={icons.CLOSE} name={icons.CLOSE}
size={18} size={18}
title={translate('Close')}
/> />
</Link> </Link>
} }

View File

@@ -82,7 +82,6 @@ class PageHeader extends Component {
aria-label="Donate" aria-label="Donate"
to="https://radarr.video/donate" to="https://radarr.video/donate"
size={14} size={14}
title={translate('Donate')}
/> />
<IconButton <IconButton
className={styles.translate} className={styles.translate}

View File

@@ -24,7 +24,6 @@ function PageHeaderActionsMenu(props) {
<MenuButton className={styles.menuButton} aria-label="Menu Button"> <MenuButton className={styles.menuButton} aria-label="Menu Button">
<Icon <Icon
name={icons.INTERACTIVE} name={icons.INTERACTIVE}
title={translate('Menu')}
/> />
</MenuButton> </MenuButton>

View File

@@ -78,8 +78,7 @@ function createMapDispatchToProps(dispatch, props) {
onImportListSyncPress() { onImportListSyncPress() {
dispatch(executeCommand({ dispatch(executeCommand({
name: commandNames.IMPORT_LIST_SYNC, name: commandNames.IMPORT_LIST_SYNC
commandFinished: this.dispatchFetchListMovies
})); }));
} }
}; };

View File

@@ -2,13 +2,10 @@ export const BOOL = 'bool';
export const BYTES = 'bytes'; export const BYTES = 'bytes';
export const DATE = 'date'; export const DATE = 'date';
export const DEFAULT = 'default'; export const DEFAULT = 'default';
export const HISTORY_EVENT_TYPE = 'historyEventType';
export const INDEXER = 'indexer'; export const INDEXER = 'indexer';
export const LANGUAGE = 'language';
export const PROTOCOL = 'protocol'; export const PROTOCOL = 'protocol';
export const QUALITY = 'quality'; export const QUALITY = 'quality';
export const QUALITY_PROFILE = 'qualityProfile'; export const QUALITY_PROFILE = 'qualityProfile';
export const MOVIE = 'movie';
export const RELEASE_STATUS = 'releaseStatus'; export const RELEASE_STATUS = 'releaseStatus';
export const MINIMUM_AVAILABILITY = 'minimumAvailability'; export const MINIMUM_AVAILABILITY = 'minimumAvailability';
export const TAG = 'tag'; export const TAG = 'tag';

View File

@@ -202,12 +202,6 @@
.headerContent { .headerContent {
padding: 15px; padding: 15px;
} }
.title {
font-weight: 300;
font-size: 30px;
line-height: 30px;
}
} }
@media only screen and (max-width: $breakpointLarge) { @media only screen and (max-width: $breakpointLarge) {

View File

@@ -44,7 +44,7 @@ import MovieCollectionLabelConnector from './../MovieCollectionLabelConnector';
import MovieCastPostersConnector from './Credits/Cast/MovieCastPostersConnector'; import MovieCastPostersConnector from './Credits/Cast/MovieCastPostersConnector';
import MovieCrewPostersConnector from './Credits/Crew/MovieCrewPostersConnector'; import MovieCrewPostersConnector from './Credits/Crew/MovieCrewPostersConnector';
import MovieDetailsLinks from './MovieDetailsLinks'; import MovieDetailsLinks from './MovieDetailsLinks';
import MovieReleaseDates from './MovieReleaseDates'; import MovieReleaseDatesConnector from './MovieReleaseDatesConnector';
import MovieStatusLabel from './MovieStatusLabel'; import MovieStatusLabel from './MovieStatusLabel';
import MovieTagsConnector from './MovieTagsConnector'; import MovieTagsConnector from './MovieTagsConnector';
import MovieTitlesTable from './Titles/MovieTitlesTable'; import MovieTitlesTable from './Titles/MovieTitlesTable';
@@ -286,7 +286,7 @@ class MovieDetails extends Component {
onMonitorTogglePress, onMonitorTogglePress,
onRefreshPress, onRefreshPress,
onSearchPress, onSearchPress,
queueItem, queueItems,
movieRuntimeFormat movieRuntimeFormat
} = this.props; } = this.props;
@@ -433,7 +433,7 @@ class MovieDetails extends Component {
} }
title={translate('ReleaseDates')} title={translate('ReleaseDates')}
body={ body={
<MovieReleaseDates <MovieReleaseDatesConnector
inCinemas={inCinemas} inCinemas={inCinemas}
physicalRelease={physicalRelease} physicalRelease={physicalRelease}
digitalRelease={digitalRelease} digitalRelease={digitalRelease}
@@ -544,7 +544,7 @@ class MovieDetails extends Component {
hasMovieFiles={hasMovieFiles} hasMovieFiles={hasMovieFiles}
monitored={monitored} monitored={monitored}
isAvailable={isAvailable} isAvailable={isAvailable}
queueItem={queueItem} queueItem={(queueItems.length > 0) ? queueItems[0] : null}
/> />
</span> </span>
</InfoLabel> </InfoLabel>
@@ -830,7 +830,7 @@ MovieDetails.propTypes = {
onRefreshPress: PropTypes.func.isRequired, onRefreshPress: PropTypes.func.isRequired,
onSearchPress: PropTypes.func.isRequired, onSearchPress: PropTypes.func.isRequired,
onGoToMovie: PropTypes.func.isRequired, onGoToMovie: PropTypes.func.isRequired,
queueItem: PropTypes.object, queueItems: PropTypes.arrayOf(PropTypes.object),
movieRuntimeFormat: PropTypes.string.isRequired movieRuntimeFormat: PropTypes.string.isRequired
}; };

View File

@@ -145,8 +145,6 @@ function createMapStateToProps() {
return acc; return acc;
}, []); }, []);
const queueItem = queueItems.find((item) => item.movieId === movie.id);
return { return {
...movie, ...movie,
alternateTitles, alternateTitles,
@@ -167,7 +165,7 @@ function createMapStateToProps() {
nextMovie, nextMovie,
isSmallScreen: dimensions.isSmallScreen, isSmallScreen: dimensions.isSmallScreen,
isSidebarVisible, isSidebarVisible,
queueItem, queueItems,
movieRuntimeFormat movieRuntimeFormat
}; };
} }

View File

@@ -0,0 +1,67 @@
import PropTypes from 'prop-types';
import React from 'react';
import Icon from 'Components/Icon';
import { icons } from 'Helpers/Props';
import getRelativeDate from 'Utilities/Date/getRelativeDate';
import styles from './MovieReleaseDates.css';
function MovieReleaseDates(props) {
const {
showRelativeDates,
shortDateFormat,
timeFormat,
inCinemas,
physicalRelease,
digitalRelease
} = props;
return (
<div>
{
!!inCinemas &&
<div >
<div className={styles.dateIcon}>
<Icon
name={icons.IN_CINEMAS}
/>
</div>
{getRelativeDate(inCinemas, shortDateFormat, showRelativeDates, { timeFormat, timeForToday: false })}
</div>
}
{
!!digitalRelease &&
<div >
<div className={styles.dateIcon}>
<Icon
name={icons.MOVIE_FILE}
/>
</div>
{getRelativeDate(digitalRelease, shortDateFormat, showRelativeDates, { timeFormat, timeForToday: false })}
</div>
}
{
!!physicalRelease &&
<div >
<div className={styles.dateIcon}>
<Icon
name={icons.DISC}
/>
</div>
{getRelativeDate(physicalRelease, shortDateFormat, showRelativeDates, { timeFormat, timeForToday: false })}
</div>
}
</div>
);
}
MovieReleaseDates.propTypes = {
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,
longDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired,
inCinemas: PropTypes.string,
physicalRelease: PropTypes.string,
digitalRelease: PropTypes.string
};
export default MovieReleaseDates;

View File

@@ -1,64 +0,0 @@
import React from 'react';
import { useSelector } from 'react-redux';
import Icon from 'Components/Icon';
import { icons } from 'Helpers/Props';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import getRelativeDate from 'Utilities/Date/getRelativeDate';
import translate from 'Utilities/String/translate';
import styles from './MovieReleaseDates.css';
interface MovieReleaseDatesProps {
inCinemas: string;
physicalRelease: string;
digitalRelease: string;
}
function MovieReleaseDates(props: MovieReleaseDatesProps) {
const { inCinemas, physicalRelease, digitalRelease } = props;
const { showRelativeDates, shortDateFormat, timeFormat } = useSelector(
createUISettingsSelector()
);
return (
<div>
{inCinemas ? (
<div title={translate('InCinemas')}>
<div className={styles.dateIcon}>
<Icon name={icons.IN_CINEMAS} />
</div>
{getRelativeDate(inCinemas, shortDateFormat, showRelativeDates, {
timeFormat,
timeForToday: false,
})}
</div>
) : null}
{digitalRelease ? (
<div title={translate('DigitalRelease')}>
<div className={styles.dateIcon}>
<Icon name={icons.MOVIE_FILE} />
</div>
{getRelativeDate(digitalRelease, shortDateFormat, showRelativeDates, {
timeFormat,
timeForToday: false,
})}
</div>
) : null}
{physicalRelease ? (
<div title={translate('PhysicalRelease')}>
<div className={styles.dateIcon}>
<Icon name={icons.DISC} />
</div>
{getRelativeDate(
physicalRelease,
shortDateFormat,
showRelativeDates,
{ timeFormat, timeForToday: false }
)}
</div>
) : null}
</div>
);
}
export default MovieReleaseDates;

View File

@@ -0,0 +1,20 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import MovieReleaseDates from './MovieReleaseDates';
function createMapStateToProps() {
return createSelector(
createUISettingsSelector(),
(uiSettings) => {
return {
showRelativeDates: uiSettings.showRelativeDates,
shortDateFormat: uiSettings.shortDateFormat,
longDateFormat: uiSettings.longDateFormat,
timeFormat: uiSettings.timeFormat
};
}
);
}
export default connect(createMapStateToProps, null)(MovieReleaseDates);

View File

@@ -8,6 +8,7 @@ import translate from 'Utilities/String/translate';
import styles from './MovieStatusLabel.css'; import styles from './MovieStatusLabel.css';
function getMovieStatus(hasFile, isMonitored, isAvailable, queueItem = false) { function getMovieStatus(hasFile, isMonitored, isAvailable, queueItem = false) {
if (queueItem) { if (queueItem) {
const queueStatus = queueItem.status; const queueStatus = queueItem.status;
const queueState = queueItem.trackedDownloadStatus; const queueState = queueItem.trackedDownloadStatus;
@@ -115,4 +116,8 @@ MovieStatusLabel.propTypes = {
colorImpairedMode: PropTypes.bool colorImpairedMode: PropTypes.bool
}; };
MovieStatusLabel.defaultProps = {
title: ''
};
export default MovieStatusLabel; export default MovieStatusLabel;

View File

@@ -100,15 +100,6 @@ function MovieIndexSortMenu(props: MovieIndexSortMenuProps) {
{translate('DigitalRelease')} {translate('DigitalRelease')}
</SortMenuItem> </SortMenuItem>
<SortMenuItem
name="releaseDate"
sortKey={sortKey}
sortDirection={sortDirection}
onPress={onSortSelect}
>
{translate('ReleaseDates')}
</SortMenuItem>
<SortMenuItem <SortMenuItem
name="tmdbRating" name="tmdbRating"
sortKey={sortKey} sortKey={sortKey}

View File

@@ -11,10 +11,7 @@ function createMovieQueueDetailsSelector(movieId: number) {
(queueItems) => { (queueItems) => {
return queueItems.reduce( return queueItems.reduce(
(acc: MovieQueueDetails, item) => { (acc: MovieQueueDetails, item) => {
if ( if (item.movieId !== movieId) {
item.trackedDownloadState === 'imported' ||
item.movieId !== movieId
) {
return acc; return acc;
} }

View File

@@ -80,12 +80,8 @@ function DownloadClientOptions(props) {
legend={translate('FailedDownloadHandling')} legend={translate('FailedDownloadHandling')}
> >
<Form> <Form>
<FormGroup <FormGroup size={sizes.MEDIUM}>
advancedSettings={advancedSettings} <FormLabel>{translate('Redownload')}</FormLabel>
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>{translate('AutoRedownloadFailed')}</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
@@ -95,28 +91,7 @@ function DownloadClientOptions(props) {
{...settings.autoRedownloadFailed} {...settings.autoRedownloadFailed}
/> />
</FormGroup> </FormGroup>
{
settings.autoRedownloadFailed.value ?
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
size={sizes.MEDIUM}
>
<FormLabel>{translate('AutoRedownloadFailedFromInteractiveSearch')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="autoRedownloadFailedFromInteractiveSearch"
helpText={translate('AutoRedownloadFailedFromInteractiveSearchHelpText')}
onChange={onInputChange}
{...settings.autoRedownloadFailedFromInteractiveSearch}
/>
</FormGroup> :
null
}
</Form> </Form>
<Alert kind={kinds.INFO}> <Alert kind={kinds.INFO}>
{translate('RemoveDownloadsAlert')} {translate('RemoveDownloadsAlert')}
</Alert> </Alert>

View File

@@ -74,15 +74,9 @@ class ImportList extends Component {
<div className={styles.enabled}> <div className={styles.enabled}>
{ {
enabled ? enabled &&
<Label kind={kinds.SUCCESS}> <Label kind={kinds.SUCCESS}>
{translate('Enabled')} {translate('Enabled')}
</Label> :
<Label
kind={kinds.DISABLED}
outline={true}
>
{translate('Disabled')}
</Label> </Label>
} }
@@ -92,6 +86,16 @@ class ImportList extends Component {
{translate('Auto')} {translate('Auto')}
</Label> </Label>
} }
{
!enabled && !enableAuto &&
<Label
kind={kinds.DISABLED}
outline={true}
>
{translate('Disabled')}
</Label>
}
</div> </div>
<div className={styles.enabled}> <div className={styles.enabled}>

View File

@@ -12,7 +12,6 @@ import translate from 'Utilities/String/translate';
import styles from './ManageImportListsEditModalContent.css'; import styles from './ManageImportListsEditModalContent.css';
interface SavePayload { interface SavePayload {
enabled?: boolean;
enableAuto?: boolean; enableAuto?: boolean;
qualityProfileId?: number; qualityProfileId?: number;
rootFolderPath?: string; rootFolderPath?: string;
@@ -26,7 +25,7 @@ interface ManageImportListsEditModalContentProps {
const NO_CHANGE = 'noChange'; const NO_CHANGE = 'noChange';
const enableOptions = [ const autoAddOptions = [
{ {
key: NO_CHANGE, key: NO_CHANGE,
get value() { get value() {
@@ -53,7 +52,6 @@ function ManageImportListsEditModalContent(
) { ) {
const { importListIds, onSavePress, onModalClose } = props; const { importListIds, onSavePress, onModalClose } = props;
const [enabled, setEnabled] = useState(NO_CHANGE);
const [enableAuto, setEnableAuto] = useState(NO_CHANGE); const [enableAuto, setEnableAuto] = useState(NO_CHANGE);
const [qualityProfileId, setQualityProfileId] = useState<string | number>( const [qualityProfileId, setQualityProfileId] = useState<string | number>(
NO_CHANGE NO_CHANGE
@@ -64,11 +62,6 @@ function ManageImportListsEditModalContent(
let hasChanges = false; let hasChanges = false;
const payload: SavePayload = {}; const payload: SavePayload = {};
if (enabled !== NO_CHANGE) {
hasChanges = true;
payload.enabled = enabled === 'enabled';
}
if (enableAuto !== NO_CHANGE) { if (enableAuto !== NO_CHANGE) {
hasChanges = true; hasChanges = true;
payload.enableAuto = enableAuto === 'enabled'; payload.enableAuto = enableAuto === 'enabled';
@@ -89,21 +82,11 @@ function ManageImportListsEditModalContent(
} }
onModalClose(); onModalClose();
}, [ }, [enableAuto, qualityProfileId, rootFolderPath, onSavePress, onModalClose]);
enabled,
enableAuto,
qualityProfileId,
rootFolderPath,
onSavePress,
onModalClose,
]);
const onInputChange = useCallback( const onInputChange = useCallback(
({ name, value }: { name: string; value: string }) => { ({ name, value }: { name: string; value: string }) => {
switch (name) { switch (name) {
case 'enabled':
setEnabled(value);
break;
case 'enableAuto': case 'enableAuto':
setEnableAuto(value); setEnableAuto(value);
break; break;
@@ -127,18 +110,6 @@ function ManageImportListsEditModalContent(
<ModalHeader>{translate('EditSelectedImportLists')}</ModalHeader> <ModalHeader>{translate('EditSelectedImportLists')}</ModalHeader>
<ModalBody> <ModalBody>
<FormGroup>
<FormLabel>{translate('Enabled')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="enabled"
value={enabled}
values={enableOptions}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup> <FormGroup>
<FormLabel>{translate('AutomaticAdd')}</FormLabel> <FormLabel>{translate('AutomaticAdd')}</FormLabel>
@@ -146,7 +117,7 @@ function ManageImportListsEditModalContent(
type={inputTypes.SELECT} type={inputTypes.SELECT}
name="enableAuto" name="enableAuto"
value={enableAuto} value={enableAuto}
values={enableOptions} values={autoAddOptions}
onChange={onInputChange} onChange={onInputChange}
/> />
</FormGroup> </FormGroup>

View File

@@ -58,12 +58,6 @@ const COLUMNS = [
isSortable: true, isSortable: true,
isVisible: true, isVisible: true,
}, },
{
name: 'enabled',
label: () => translate('Enabled'),
isSortable: true,
isVisible: true,
},
{ {
name: 'enableAuto', name: 'enableAuto',
label: () => translate('AutomaticAdd'), label: () => translate('AutomaticAdd'),

View File

@@ -1,6 +1,5 @@
.name, .name,
.tags, .tags,
.enabled,
.enableAuto, .enableAuto,
.qualityProfileId, .qualityProfileId,
.rootFolderPath, .rootFolderPath,
@@ -8,4 +7,4 @@
composes: cell from '~Components/Table/Cells/TableRowCell.css'; composes: cell from '~Components/Table/Cells/TableRowCell.css';
word-break: break-all; word-break: break-all;
} }

View File

@@ -2,7 +2,6 @@
// Please do not change this file! // Please do not change this file!
interface CssExports { interface CssExports {
'enableAuto': string; 'enableAuto': string;
'enabled': string;
'implementation': string; 'implementation': string;
'name': string; 'name': string;
'qualityProfileId': string; 'qualityProfileId': string;

View File

@@ -7,7 +7,6 @@ import TableRow from 'Components/Table/TableRow';
import TagListConnector from 'Components/TagListConnector'; import TagListConnector from 'Components/TagListConnector';
import { createQualityProfileSelectorForHook } from 'Store/Selectors/createQualityProfileSelector'; import { createQualityProfileSelectorForHook } from 'Store/Selectors/createQualityProfileSelector';
import { SelectStateInputProps } from 'typings/props'; import { SelectStateInputProps } from 'typings/props';
import translate from 'Utilities/String/translate';
import styles from './ManageImportListsModalRow.css'; import styles from './ManageImportListsModalRow.css';
interface ManageImportListsModalRowProps { interface ManageImportListsModalRowProps {
@@ -17,7 +16,6 @@ interface ManageImportListsModalRowProps {
qualityProfileId: number; qualityProfileId: number;
implementation: string; implementation: string;
tags: number[]; tags: number[];
enabled: boolean;
enableAuto: boolean; enableAuto: boolean;
columns: Column[]; columns: Column[];
isSelected?: boolean; isSelected?: boolean;
@@ -32,7 +30,6 @@ function ManageImportListsModalRow(props: ManageImportListsModalRowProps) {
rootFolderPath, rootFolderPath,
qualityProfileId, qualityProfileId,
implementation, implementation,
enabled,
enableAuto, enableAuto,
tags, tags,
onSelectedChange, onSelectedChange,
@@ -66,19 +63,15 @@ function ManageImportListsModalRow(props: ManageImportListsModalRowProps) {
</TableRowCell> </TableRowCell>
<TableRowCell className={styles.qualityProfileId}> <TableRowCell className={styles.qualityProfileId}>
{qualityProfile?.name ?? translate('None')} {qualityProfile?.name ?? 'None'}
</TableRowCell> </TableRowCell>
<TableRowCell className={styles.rootFolderPath}> <TableRowCell className={styles.rootFolderPath}>
{rootFolderPath} {rootFolderPath}
</TableRowCell> </TableRowCell>
<TableRowCell className={styles.enabled}>
{enabled ? translate('Yes') : translate('No')}
</TableRowCell>
<TableRowCell className={styles.enableAuto}> <TableRowCell className={styles.enableAuto}>
{enableAuto ? translate('Yes') : translate('No')} {enableAuto ? 'Yes' : 'No'}
</TableRowCell> </TableRowCell>
<TableRowCell className={styles.tags}> <TableRowCell className={styles.tags}>

View File

@@ -6,8 +6,6 @@ import getSectionState from 'Utilities/State/getSectionState';
import { set, updateServerSideCollection } from '../baseActions'; import { set, updateServerSideCollection } from '../baseActions';
function createFetchServerSideCollectionHandler(section, url, fetchDataAugmenter) { function createFetchServerSideCollectionHandler(section, url, fetchDataAugmenter) {
const [baseSection] = section.split('.');
return function(getState, payload, dispatch) { return function(getState, payload, dispatch) {
dispatch(set({ section, isFetching: true })); dispatch(set({ section, isFetching: true }));
@@ -27,13 +25,10 @@ function createFetchServerSideCollectionHandler(section, url, fetchDataAugmenter
const { const {
selectedFilterKey, selectedFilterKey,
filters filters,
customFilters
} = sectionState; } = sectionState;
const customFilters = getState().customFilters.items.filter((customFilter) => {
return customFilter.type === section || customFilter.type === baseSection;
});
const selectedFilters = findSelectedFilters(selectedFilterKey, filters, customFilters); const selectedFilters = findSelectedFilters(selectedFilterKey, filters, customFilters);
selectedFilters.forEach((filter) => { selectedFilters.forEach((filter) => {
@@ -42,8 +37,7 @@ function createFetchServerSideCollectionHandler(section, url, fetchDataAugmenter
const promise = createAjaxRequest({ const promise = createAjaxRequest({
url, url,
data, data
traditional: true
}).request; }).request;
promise.done((response) => { promise.done((response) => {

View File

@@ -49,6 +49,8 @@ export const defaultState = {
selectedFilterKey: 'monitored', selectedFilterKey: 'monitored',
customFilters: [],
filters: [ filters: [
{ {
key: 'all', key: 'all',

View File

@@ -9,7 +9,7 @@ import getNewMovie from 'Utilities/Movie/getNewMovie';
import getSectionState from 'Utilities/State/getSectionState'; import getSectionState from 'Utilities/State/getSectionState';
import updateSectionState from 'Utilities/State/updateSectionState'; import updateSectionState from 'Utilities/State/updateSectionState';
import translate from 'Utilities/String/translate'; 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 createHandleActions from './Creators/createHandleActions';
import createClearReducer from './Creators/Reducers/createClearReducer'; import createClearReducer from './Creators/Reducers/createClearReducer';
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer'; import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
@@ -507,11 +507,11 @@ export const actionHandlers = handleThunks({
}).request; }).request;
promise.done((data) => { 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 })); data = data.map((movie) => ({ ...movie, id: movie.tmdbId }));
dispatch(batchActions([ dispatch(batchActions([
update({ section, data }), ...data.map((movie) => updateItem({ section, ...movie })),
set({ set({
section, section,

View File

@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { createAction } from 'redux-actions'; import { createAction } from 'redux-actions';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import { filterBuilderTypes, filterBuilderValueTypes, filterTypes, icons, sortDirections } from 'Helpers/Props'; import { filterTypes, icons, sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks'; import { createThunk, handleThunks } from 'Store/thunks';
import createAjaxRequest from 'Utilities/createAjaxRequest'; import createAjaxRequest from 'Utilities/createAjaxRequest';
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers'; import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
@@ -177,33 +177,6 @@ export const defaultState = {
} }
] ]
} }
],
filterBuilderProps: [
{
name: 'eventType',
label: () => translate('EventType'),
type: filterBuilderTypes.EQUAL,
valueType: filterBuilderValueTypes.HISTORY_EVENT_TYPE
},
{
name: 'movieIds',
label: () => translate('Movie'),
type: filterBuilderTypes.EQUAL,
valueType: filterBuilderValueTypes.MOVIE
},
{
name: 'quality',
label: () => translate('Quality'),
type: filterBuilderTypes.EQUAL,
valueType: filterBuilderValueTypes.QUALITY
},
{
name: 'languages',
label: () => translate('Languages'),
type: filterBuilderTypes.CONTAINS,
valueType: filterBuilderValueTypes.LANGUAGE
}
] ]
}; };

View File

@@ -28,8 +28,8 @@ export const defaultState = {
isReprocessing: false, isReprocessing: false,
error: null, error: null,
items: [], items: [],
sortKey: 'relativePath', sortKey: 'quality',
sortDirection: sortDirections.ASCENDING, sortDirection: sortDirections.DESCENDING,
recentFolders: [], recentFolders: [],
importMode: 'chooseImportMode', importMode: 'chooseImportMode',
sortPredicates: { sortPredicates: {

View File

@@ -1,5 +1,4 @@
import _ from 'lodash'; import _ from 'lodash';
import moment from 'moment';
import { createAction } from 'redux-actions'; import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions'; import { batchActions } from 'redux-batched-actions';
import { filterTypePredicates, filterTypes, sortDirections } from 'Helpers/Props'; import { filterTypePredicates, filterTypes, sortDirections } from 'Helpers/Props';
@@ -241,55 +240,16 @@ export const sortPredicates = {
return item.year || undefined; return item.year || undefined;
}, },
inCinemas: function(item, direction) { inCinemas: function(item) {
if (item.inCinemas) { return item.inCinemas || '';
return moment(item.inCinemas).unix();
}
if (direction === sortDirections.DESCENDING) {
return -1 * Number.MAX_VALUE;
}
return Number.MAX_VALUE;
}, },
physicalRelease: function(item, direction) { physicalRelease: function(item) {
if (item.physicalRelease) { return item.physicalRelease || '';
return moment(item.physicalRelease).unix();
}
if (direction === sortDirections.DESCENDING) {
return -1 * Number.MAX_VALUE;
}
return Number.MAX_VALUE;
}, },
digitalRelease: function(item, direction) { digitalRelease: function(item) {
if (item.digitalRelease) { return item.digitalRelease || '';
return moment(item.digitalRelease).unix();
}
if (direction === sortDirections.DESCENDING) {
return -1 * Number.MAX_VALUE;
}
return Number.MAX_VALUE;
},
releaseDate: function(item, direction) {
const { inCinemas, digitalRelease, physicalRelease } = item;
const releaseDate = digitalRelease || physicalRelease || inCinemas;
if (releaseDate) {
return moment(releaseDate).unix();
}
if (direction === sortDirections.DESCENDING) {
return -1 * Number.MAX_VALUE;
}
return Number.MAX_VALUE;
} }
}; };

View File

@@ -4,7 +4,7 @@ import React from 'react';
import { createAction } from 'redux-actions'; import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions'; import { batchActions } from 'redux-batched-actions';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import { filterBuilderTypes, filterBuilderValueTypes, icons, sortDirections } from 'Helpers/Props'; import { icons, sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks'; import { createThunk, handleThunks } from 'Store/thunks';
import createAjaxRequest from 'Utilities/createAjaxRequest'; import createAjaxRequest from 'Utilities/createAjaxRequest';
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers'; import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
@@ -159,43 +159,6 @@ export const defaultState = {
isVisible: true, isVisible: true,
isModifiable: false isModifiable: false
} }
],
selectedFilterKey: 'all',
filters: [
{
key: 'all',
label: 'All',
filters: []
}
],
filterBuilderProps: [
{
name: 'movieIds',
label: () => translate('Movie'),
type: filterBuilderTypes.EQUAL,
valueType: filterBuilderValueTypes.MOVIE
},
{
name: 'quality',
label: () => translate('Quality'),
type: filterBuilderTypes.EQUAL,
valueType: filterBuilderValueTypes.QUALITY
},
{
name: 'languages',
label: () => translate('Languages'),
type: filterBuilderTypes.CONTAINS,
valueType: filterBuilderValueTypes.LANGUAGE
},
{
name: 'protocol',
label: () => translate('Protocol'),
type: filterBuilderTypes.EQUAL,
valueType: filterBuilderValueTypes.PROTOCOL
}
] ]
}, },
sortPredicates: { sortPredicates: {
@@ -210,8 +173,7 @@ export const persistState = [
'queue.paged.pageSize', 'queue.paged.pageSize',
'queue.paged.sortKey', 'queue.paged.sortKey',
'queue.paged.sortDirection', 'queue.paged.sortDirection',
'queue.paged.columns', 'queue.paged.columns'
'queue.paged.selectedFilterKey'
]; ];
// //
@@ -236,7 +198,6 @@ export const GOTO_NEXT_QUEUE_PAGE = 'queue/gotoQueueNextPage';
export const GOTO_LAST_QUEUE_PAGE = 'queue/gotoQueueLastPage'; export const GOTO_LAST_QUEUE_PAGE = 'queue/gotoQueueLastPage';
export const GOTO_QUEUE_PAGE = 'queue/gotoQueuePage'; export const GOTO_QUEUE_PAGE = 'queue/gotoQueuePage';
export const SET_QUEUE_SORT = 'queue/setQueueSort'; export const SET_QUEUE_SORT = 'queue/setQueueSort';
export const SET_QUEUE_FILTER = 'queue/setQueueFilter';
export const SET_QUEUE_TABLE_OPTION = 'queue/setQueueTableOption'; export const SET_QUEUE_TABLE_OPTION = 'queue/setQueueTableOption';
export const SET_QUEUE_OPTION = 'queue/setQueueOption'; export const SET_QUEUE_OPTION = 'queue/setQueueOption';
export const CLEAR_QUEUE = 'queue/clearQueue'; export const CLEAR_QUEUE = 'queue/clearQueue';
@@ -261,7 +222,6 @@ export const gotoQueueNextPage = createThunk(GOTO_NEXT_QUEUE_PAGE);
export const gotoQueueLastPage = createThunk(GOTO_LAST_QUEUE_PAGE); export const gotoQueueLastPage = createThunk(GOTO_LAST_QUEUE_PAGE);
export const gotoQueuePage = createThunk(GOTO_QUEUE_PAGE); export const gotoQueuePage = createThunk(GOTO_QUEUE_PAGE);
export const setQueueSort = createThunk(SET_QUEUE_SORT); export const setQueueSort = createThunk(SET_QUEUE_SORT);
export const setQueueFilter = createThunk(SET_QUEUE_FILTER);
export const setQueueTableOption = createAction(SET_QUEUE_TABLE_OPTION); export const setQueueTableOption = createAction(SET_QUEUE_TABLE_OPTION);
export const setQueueOption = createAction(SET_QUEUE_OPTION); export const setQueueOption = createAction(SET_QUEUE_OPTION);
export const clearQueue = createAction(CLEAR_QUEUE); export const clearQueue = createAction(CLEAR_QUEUE);
@@ -308,8 +268,7 @@ export const actionHandlers = handleThunks({
[serverSideCollectionHandlers.NEXT_PAGE]: GOTO_NEXT_QUEUE_PAGE, [serverSideCollectionHandlers.NEXT_PAGE]: GOTO_NEXT_QUEUE_PAGE,
[serverSideCollectionHandlers.LAST_PAGE]: GOTO_LAST_QUEUE_PAGE, [serverSideCollectionHandlers.LAST_PAGE]: GOTO_LAST_QUEUE_PAGE,
[serverSideCollectionHandlers.EXACT_PAGE]: GOTO_QUEUE_PAGE, [serverSideCollectionHandlers.EXACT_PAGE]: GOTO_QUEUE_PAGE,
[serverSideCollectionHandlers.SORT]: SET_QUEUE_SORT, [serverSideCollectionHandlers.SORT]: SET_QUEUE_SORT
[serverSideCollectionHandlers.FILTER]: SET_QUEUE_FILTER
}, },
fetchDataAugmenter fetchDataAugmenter
), ),

View File

@@ -0,0 +1,17 @@
import { createSelector } from 'reselect';
function createCollectionSelector() {
return createSelector(
(state, { collectionId }) => collectionId,
(state) => state.movieCollections.itemMap,
(state) => state.movieCollections.items,
(collectionId, itemMap, allCollections) => {
if (allCollections && itemMap && collectionId in itemMap) {
return allCollections[itemMap[collectionId]];
}
return undefined;
}
);
}
export default createCollectionSelector;

View File

@@ -1,17 +0,0 @@
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
function createCollectionSelector() {
return createSelector(
(_: AppState, { collectionId }: { collectionId: number }) => collectionId,
(state: AppState) => state.movieCollections.itemMap,
(state: AppState) => state.movieCollections.items,
(collectionId, itemMap, allCollections) => {
return allCollections && itemMap && collectionId in itemMap
? allCollections[itemMap[collectionId]]
: undefined;
}
);
}
export default createCollectionSelector;

View File

@@ -70,7 +70,7 @@ module.exports = {
// Toolbar // Toolbar
toolbarColor: '#e1e2e3', toolbarColor: '#e1e2e3',
toolbarBackgroundColor: '#262626', toolbarBackgroundColor: '#262626',
toolbarMenuItemBackgroundColor: '#303030', toolbarMenuItemBackgroundColor: '#606060',
toolbarMenuItemHoverBackgroundColor: '#515151', toolbarMenuItemHoverBackgroundColor: '#515151',
toolbarLabelColor: '#e1e2e3', toolbarLabelColor: '#e1e2e3',

View File

@@ -116,7 +116,6 @@ class BackupRow extends Component {
<TableRowCell className={styles.actions}> <TableRowCell className={styles.actions}>
<IconButton <IconButton
title={translate('RestoreBackup')}
name={icons.RESTORE} name={icons.RESTORE}
onPress={this.onRestorePress} onPress={this.onRestorePress}
/> />
@@ -139,9 +138,7 @@ class BackupRow extends Component {
isOpen={isConfirmDeleteModalOpen} isOpen={isConfirmDeleteModalOpen}
kind={kinds.DANGER} kind={kinds.DANGER}
title={translate('DeleteBackup')} title={translate('DeleteBackup')}
message={translate('DeleteBackupMessageText', { message={translate('DeleteBackupMessageText', { name })}
name
})}
confirmLabel={translate('Delete')} confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeletePress} onConfirm={this.onConfirmDeletePress}
onCancel={this.onConfirmDeleteModalClose} onCancel={this.onConfirmDeleteModalClose}

View File

@@ -109,7 +109,7 @@ class Backups extends Component {
{ {
!isFetching && !!error && !isFetching && !!error &&
<Alert kind={kinds.DANGER}> <Alert kind={kinds.DANGER}>
{translate('BackupsLoadError')} {translate('UnableToLoadBackups')}
</Alert> </Alert>
} }

View File

@@ -146,9 +146,7 @@ class RestoreBackupModalContent extends Component {
<ModalBody> <ModalBody>
{ {
!!id && translate('WouldYouLikeToRestoreBackup', { !!id && translate('WouldYouLikeToRestoreBackup', { name })
name
})
} }
{ {

View File

@@ -1,8 +1,8 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Alert from 'Components/Alert'; import Alert from 'Components/Alert';
import Link from 'Components/Link/Link';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
import PageContent from 'Components/Page/PageContent'; import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody'; import PageContentBody from 'Components/Page/PageContentBody';
import PageToolbar from 'Components/Page/Toolbar/PageToolbar'; import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
@@ -67,7 +67,7 @@ class LogFiles extends Component {
/> />
<PageToolbarButton <PageToolbarButton
label={translate('Clear')} label={translate('Delete')}
iconName={icons.CLEAR} iconName={icons.CLEAR}
isSpinning={deleteFilesExecuting} isSpinning={deleteFilesExecuting}
onPress={onDeleteFilesPress} onPress={onDeleteFilesPress}
@@ -77,15 +77,13 @@ class LogFiles extends Component {
<PageContentBody> <PageContentBody>
<Alert> <Alert>
<div> <div>
{translate('LogFilesLocation', { Log files are located in: {location}
location
})}
</div> </div>
{ {
currentLogView === 'Log Files' && currentLogView === 'Log Files' &&
<div> <div>
<InlineMarkdown data={translate('TheLogLevelDefault')} /> {translate('TheLogLevelDefault')} <Link to="/settings/general">{translate('GeneralSettings')}</Link>
</div> </div>
} }
</Alert> </Alert>

View File

@@ -4,7 +4,6 @@ import Menu from 'Components/Menu/Menu';
import MenuButton from 'Components/Menu/MenuButton'; import MenuButton from 'Components/Menu/MenuButton';
import MenuContent from 'Components/Menu/MenuContent'; import MenuContent from 'Components/Menu/MenuContent';
import MenuItem from 'Components/Menu/MenuItem'; import MenuItem from 'Components/Menu/MenuItem';
import translate from 'Utilities/String/translate';
class LogsNavMenu extends Component { class LogsNavMenu extends Component {
@@ -51,13 +50,13 @@ class LogsNavMenu extends Component {
<MenuItem <MenuItem
to={'/system/logs/files'} to={'/system/logs/files'}
> >
{translate('LogFiles')} Log Files
</MenuItem> </MenuItem>
<MenuItem <MenuItem
to={'/system/logs/files/update'} to={'/system/logs/files/update'}
> >
{translate('UpdaterLogFiles')} Updater Log Files
</MenuItem> </MenuItem>
</MenuContent> </MenuContent>
</Menu> </Menu>

View File

@@ -45,14 +45,7 @@ class About extends Component {
packageVersion && packageVersion &&
<DescriptionListItem <DescriptionListItem
title={translate('PackageVersion')} title={translate('PackageVersion')}
data={(packageAuthor ? data={(packageAuthor ? <span> {packageVersion} {' by '} <InlineMarkdown data={packageAuthor} /> </span> : packageVersion)}
<InlineMarkdown data={translate('PackageVersionInfo', {
packageVersion,
packageAuthor
})}
/> :
packageVersion
)}
/> />
} }

View File

@@ -72,7 +72,6 @@ function getInternalLink(source) {
function getTestLink(source, props) { function getTestLink(source, props) {
switch (source) { switch (source) {
case 'IndexerStatusCheck': case 'IndexerStatusCheck':
case 'IndexerLongTermStatusCheck':
return ( return (
<SpinnerIconButton <SpinnerIconButton
name={icons.TEST} name={icons.TEST}
@@ -153,7 +152,7 @@ class Health extends Component {
{ {
!healthIssues && !healthIssues &&
<div className={styles.healthOk}> <div className={styles.healthOk}>
{translate('NoIssuesWithYourConfiguration')} {translate('HealthNoIssues')}
</div> </div>
} }

View File

@@ -26,7 +26,7 @@ class MoreInfo extends Component {
{translate('Wiki')} {translate('Wiki')}
</DescriptionListItemTitle> </DescriptionListItemTitle>
<DescriptionListItemDescription> <DescriptionListItemDescription>
<Link to="https://wiki.servarr.com/radarr">wiki.servarr.com/radarr</Link> <Link to="https://wiki.servarr.com/radarr">{translate('Wiki')}</Link>
</DescriptionListItemDescription> </DescriptionListItemDescription>
<DescriptionListItemTitle> <DescriptionListItemTitle>

View File

@@ -44,7 +44,7 @@ class Updates extends Component {
const hasUpdateToInstall = hasUpdates && _.some(items, { installable: true, latest: true }); const hasUpdateToInstall = hasUpdates && _.some(items, { installable: true, latest: true });
const noUpdateToInstall = hasUpdates && !hasUpdateToInstall; const noUpdateToInstall = hasUpdates && !hasUpdateToInstall;
const externalUpdaterPrefix = translate('UpdateRadarrDirectlyLoadError'); const externalUpdaterPrefix = translate('UnableToUpdateRadarrDirectly');
const externalUpdaterMessages = { const externalUpdaterMessages = {
external: translate('ExternalUpdater'), external: translate('ExternalUpdater'),
apt: translate('AptUpdater'), apt: translate('AptUpdater'),
@@ -176,7 +176,7 @@ class Updates extends Component {
kind={kinds.INVERSE} kind={kinds.INVERSE}
title={formatDateTime(update.installedOn, longDateFormat, timeFormat)} title={formatDateTime(update.installedOn, longDateFormat, timeFormat)}
> >
{translate('PreviouslyInstalled')} Previously Installed
</Label> : </Label> :
null null
} }
@@ -213,14 +213,14 @@ class Updates extends Component {
{ {
!!updatesError && !!updatesError &&
<div> <div>
{translate('FailedToFetchUpdates')} Failed to fetch updates
</div> </div>
} }
{ {
!!generalSettingsError && !!generalSettingsError &&
<div> <div>
{translate('FailedToUpdateSettings')} Failed to update settings
</div> </div>
} }
</PageContentBody> </PageContentBody>

View File

@@ -25,18 +25,20 @@ export async function fetchTranslations(): Promise<boolean> {
export default function translate( export default function translate(
key: string, key: string,
tokens: Record<string, string | number | boolean> = {} tokens?: Record<string, string | number | boolean>
) { ) {
const translation = translations[key] || key; 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 return translation.replace(/\{([a-z0-9]+?)\}/gi, (match, tokenMatch) =>
Object.values(tokens).forEach((value, index) => { String(tokens[tokenMatch] ?? match)
tokens[index] = value; );
}); }
return translation.replace(/\{([a-z0-9]+?)\}/gi, (match, tokenMatch) => return translation;
String(tokens[tokenMatch] ?? match)
);
} }

View File

@@ -1,27 +0,0 @@
import Language from 'Language/Language';
import { QualityModel } from 'Quality/Quality';
import CustomFormat from './CustomFormat';
export type HistoryEventType =
| 'grabbed'
| 'downloadFolderImported'
| 'downloadFailed'
| 'movieFileDeleted'
| 'movieFolderImported'
| 'movieFileRenamed'
| 'downloadIgnored';
export default interface History {
movieId: number;
sourceTitle: string;
languages: Language[];
quality: QualityModel;
customFormats: CustomFormat[];
customFormatScore: number;
qualityCutoffNotMet: boolean;
date: string;
downloadId: string;
eventType: HistoryEventType;
data: unknown;
id: number;
}

View File

@@ -12,7 +12,6 @@ export interface Field {
interface ImportList extends ModelBase { interface ImportList extends ModelBase {
enable: boolean; enable: boolean;
enabled: boolean;
enableAuto: boolean; enableAuto: boolean;
qualityProfileId: number; qualityProfileId: number;
rootFolderPath: string; rootFolderPath: string;

View File

@@ -1,43 +0,0 @@
import ModelBase from 'App/ModelBase';
import Language from 'Language/Language';
import { QualityModel } from 'Quality/Quality';
import CustomFormat from 'typings/CustomFormat';
export type QueueTrackedDownloadStatus = 'ok' | 'warning' | 'error';
export type QueueTrackedDownloadState =
| 'downloading'
| 'importPending'
| 'importing'
| 'imported'
| 'failedPending'
| 'failed'
| 'ignored';
export interface StatusMessage {
title: string;
messages: string[];
}
interface Queue extends ModelBase {
languages: Language[];
quality: QualityModel;
customFormats: CustomFormat[];
size: number;
title: string;
sizeleft: number;
timeleft: string;
estimatedCompletionTime: string;
status: string;
trackedDownloadStatus: QueueTrackedDownloadStatus;
trackedDownloadState: QueueTrackedDownloadState;
statusMessages: StatusMessage[];
errorMessage: string;
downloadId: string;
protocol: string;
downloadClient: string;
outputPath: string;
movieHasFile: boolean;
movieId?: number;
}
export default Queue;

View File

@@ -73,15 +73,15 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@"[Info] MigrationController: *** Migrating Database=radarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")] [TestCase(@"[Info] MigrationController: *** Migrating Database=radarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
// Announce URLs (passkeys) Magnet & Tracker // Announce URLs (passkeys) Magnet & Tracker
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210IMAveQL2tyu8xyui%2fannounce""}")] [TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")]
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2ftracker.php%2f9pr04sg601233210IMAveQL2tyu8xyui%2fannounce""}")] [TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2ftracker.php%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")]
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2fannounce%2f9pr04sg601233210IMAveQL2tyu8xyui""}")] [TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2fannounce%2f9pr04sg601233210imaveql2tyu8xyui""}")]
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2fannounce.php%3fpasskey%3d9pr04sg601233210IMAveQL2tyu8xyui""}")] [TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2fannounce.php%3fpasskey%3d9pr04sg601233210imaveql2tyu8xyui""}")]
[TestCase(@"tracker"":""https://xxx.yyy/9pr04sg601233210IMAveQL2tyu8xyui/announce""}")] [TestCase(@"tracker"":""https://xxx.yyy/9pr04sg601233210imaveql2tyu8xyui/announce""}")]
[TestCase(@"tracker"":""https://xxx.yyy/tracker.php/9pr04sg601233210IMAveQL2tyu8xyui/announce""}")] [TestCase(@"tracker"":""https://xxx.yyy/tracker.php/9pr04sg601233210imaveql2tyu8xyui/announce""}")]
[TestCase(@"tracker"":""https://xxx.yyy/announce/9pr04sg601233210IMAveQL2tyu8xyui""}")] [TestCase(@"tracker"":""https://xxx.yyy/announce/9pr04sg601233210imaveql2tyu8xyui""}")]
[TestCase(@"tracker"":""https://xxx.yyy/announce.php?passkey=9pr04sg601233210IMAveQL2tyu8xyui""}")] [TestCase(@"tracker"":""https://xxx.yyy/announce.php?passkey=9pr04sg601233210imaveql2tyu8xyui""}")]
[TestCase(@"tracker"":""http://xxx.yyy/announce.php?passkey=9pr04sg601233210IMAveQL2tyu8xyui"",""info"":""http://xxx.yyy/info?a=b""")] [TestCase(@"tracker"":""http://xxx.yyy/announce.php?passkey=9pr04sg601233210imaveql2tyu8xyui"",""info"":""http://xxx.yyy/info?a=b""")]
// Notifiarr // Notifiarr
[TestCase(@"https://xxx.yyy/api/v1/notification/radarr/9pr04sg6-0123-3210-imav-eql2tyu8xyui")] [TestCase(@"https://xxx.yyy/api/v1/notification/radarr/9pr04sg6-0123-3210-imav-eql2tyu8xyui")]

View File

@@ -20,7 +20,7 @@ namespace NzbDrone.Common.Instrumentation
new (@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase), new (@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
// Trackers Announce Keys; Designed for Qbit Json; should work for all in theory // Trackers Announce Keys; Designed for Qbit Json; should work for all in theory
new (@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce", RegexOptions.Compiled | RegexOptions.IgnoreCase), new (@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"),
// Path // Path
new (@"C:\\Users\\(?<secret>[^\""]+?)(\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase), new (@"C:\\Users\\(?<secret>[^\""]+?)(\\|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),

View File

@@ -12,7 +12,7 @@ namespace NzbDrone.Common.Instrumentation
{ {
public static class NzbDroneLogger public static class NzbDroneLogger
{ {
private const string FILE_LOG_LAYOUT = @"${date:format=yyyy-MM-dd HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}"; private const string FILE_LOG_LAYOUT = @"${date:format=yyyy-MM-dd HH\:mm\:ss.fff}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}${exception:format=Data}${newline}}";
private static bool _isConfigured; private static bool _isConfigured;

View File

@@ -16,7 +16,7 @@
<PackageReference Include="SharpZipLib" Version="1.3.3" /> <PackageReference Include="SharpZipLib" Version="1.3.3" />
<PackageReference Include="System.Text.Json" Version="6.0.8" /> <PackageReference Include="System.Text.Json" Version="6.0.8" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" /> <PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.118-22" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.1" /> <PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.1" />
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" /> <PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" /> <PackageReference Include="System.Runtime.Loader" Version="4.3.0" />

View File

@@ -72,27 +72,15 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse();
} }
[TestCase("Series Title S02 Disc 1 1080i Blu-ray DTS-HD MA 2.0 AVC-TrollHD")] [TestCase("How the Earth Was Made S02 Disc 1 1080i Blu-ray DTS-HD MA 2.0 AVC-TrollHD")]
[TestCase("Series Title S03 Disc 1 1080p Blu-ray LPCM 2.0 AVC-TrollHD")] [TestCase("The Universe S03 Disc 1 1080p Blu-ray LPCM 2.0 AVC-TrollHD")]
[TestCase("SERIES TITLE S02 1080P FULL BLURAY AVC DTS-HD MA 5 1")] [TestCase("HELL ON WHEELS S02 1080P FULL BLURAY AVC DTS-HD MA 5 1")]
[TestCase("Series.Title.S06.2016.DISC.3.BluRay.1080p.AVC.Atmos.TrueHD7.1-MTeam")] [TestCase("Game.of.Thrones.S06.2016.DISC.3.BluRay.1080p.AVC.Atmos.TrueHD7.1-MTeam")]
[TestCase("Series Title S05 Disc 1 BluRay 1080p AVC Atmos TrueHD 7 1-MTeam")] [TestCase("Game of Thrones S05 Disc 1 BluRay 1080p AVC Atmos TrueHD 7 1-MTeam")]
[TestCase("Series Title S05 Disc 1 BluRay 1080p AVC Atmos TrueHD 7 1-MTeam")]
[TestCase("Someone.the.Entertainer.Presents.S01.NTSC.3xDVD9.MPEG-2.DD2.0")]
[TestCase("Series.Title.S00.The.Christmas.Special.2011.PAL.DVD5.DD2.0")]
[TestCase("Series.of.Desire.2000.S1_D01.NTSC.DVD5")]
public void should_return_false_if_matches_disc_format(string title) public void should_return_false_if_matches_disc_format(string title)
{ {
_remoteMovie.Release.Title = title; _remoteMovie.Release.Title = title;
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse();
} }
[TestCase("Series Title EP50 USLT NTSC DVDRemux DD2.0")]
[TestCase("Series.Title.S01.NTSC.DVDRip.DD2.0.x264-PLAiD")]
public void should_return_true_if_dvdrip(string title)
{
_remoteMovie.Release.Title = title;
Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue();
}
} }
} }

View File

@@ -68,16 +68,5 @@ namespace NzbDrone.Core.Test.IndexerTests
VerifyNoUpdate(); VerifyNoUpdate();
} }
[Test]
public void should_not_record_failure_for_unknown_provider()
{
Subject.RecordFailure(0);
Mocker.GetMock<IIndexerStatusRepository>()
.Verify(v => v.FindByProviderId(1), Times.Never);
VerifyNoUpdate();
}
} }
} }

View File

@@ -24,28 +24,35 @@ namespace NzbDrone.Core.Test.Localization
[Test] [Test]
public void should_get_string_in_dictionary_if_lang_exists_and_string_exists() public void should_get_string_in_dictionary_if_lang_exists_and_string_exists()
{ {
var localizedString = Subject.GetLocalizedString("UILanguage"); var localizedString = Subject.GetLocalizedString("UiLanguage");
localizedString.Should().Be("UI Language"); localizedString.Should().Be("UI Language");
} }
[Test] [Test]
public void should_get_string_in_french() public void should_get_string_in_default_language_dictionary_if_no_lang_country_code_exists_and_string_exists()
{ {
Mocker.GetMock<IConfigService>().Setup(m => m.UILanguage).Returns((int)Language.French); var localizedString = Subject.GetLocalizedString("UiLanguage", "fr_fr");
var localizedString = Subject.GetLocalizedString("UILanguage");
localizedString.Should().Be("Langue de l'IU"); localizedString.Should().Be("Langue de l'IU");
ExceptionVerification.ExpectedErrors(0); ExceptionVerification.ExpectedErrors(1);
} }
[Test] [Test]
public void should_get_string_in_default_dictionary_if_unknown_language_and_string_exists() public void should_get_string_in_default_dictionary_if_no_lang_exists_and_string_exists()
{ {
Mocker.GetMock<IConfigService>().Setup(m => m.UILanguage).Returns(0); var localizedString = Subject.GetLocalizedString("UiLanguage", "an");
var localizedString = Subject.GetLocalizedString("UILanguage");
localizedString.Should().Be("UI Language");
ExceptionVerification.ExpectedErrors(1);
}
[Test]
public void should_get_string_in_default_dictionary_if_lang_empty_and_string_exists()
{
var localizedString = Subject.GetLocalizedString("UiLanguage", "");
localizedString.Should().Be("UI Language"); localizedString.Should().Be("UI Language");
} }
@@ -53,7 +60,7 @@ namespace NzbDrone.Core.Test.Localization
[Test] [Test]
public void should_return_argument_if_string_doesnt_exists() public void should_return_argument_if_string_doesnt_exists()
{ {
var localizedString = Subject.GetLocalizedString("badString"); var localizedString = Subject.GetLocalizedString("badString", "en");
localizedString.Should().Be("badString"); localizedString.Should().Be("badString");
} }

View File

@@ -37,12 +37,6 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
[TestCase("The Mist", "M", "The Mist")] [TestCase("The Mist", "M", "The Mist")]
[TestCase("A", "A", "A")] [TestCase("A", "A", "A")]
[TestCase("30 Rock", "3", "30 Rock")] [TestCase("30 Rock", "3", "30 Rock")]
[TestCase("The '80s Greatest", "8", "The '80s Greatest")]
[TestCase("좀비버스", "좀", "좀비버스")]
[TestCase("¡Mucha Lucha!", "M", "¡Mucha Lucha!")]
[TestCase(".hack", "H", "hack")]
[TestCase("Ütopya", "U", "Ütopya")]
[TestCase("Æon Flux", "A", "Æon Flux")]
public void should_get_expected_folder_name_back(string title, string parent, string child) public void should_get_expected_folder_name_back(string title, string parent, string child)
{ {
_movie.Title = title; _movie.Title = title;

View File

@@ -219,7 +219,6 @@ namespace NzbDrone.Core.Test.ParserTests
} }
[TestCase("Movie.Title.1994.Vietnamese.1080p.XviD-LOL")] [TestCase("Movie.Title.1994.Vietnamese.1080p.XviD-LOL")]
[TestCase("Movie.Title.1994.VIE.1080p.XviD-LOL")]
public void should_parse_language_vietnamese(string postTitle) public void should_parse_language_vietnamese(string postTitle)
{ {
var result = Parser.Parser.ParseMovieTitle(postTitle, true); var result = Parser.Parser.ParseMovieTitle(postTitle, true);

View File

@@ -364,7 +364,6 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("German.Only.Movie.2021.French.1080p.BluRay.AVC-UNTAVC")] [TestCase("German.Only.Movie.2021.French.1080p.BluRay.AVC-UNTAVC")]
[TestCase("Movie.Title.2008.US.Directors.Cut.UHD.BD66.Blu-ray")] [TestCase("Movie.Title.2008.US.Directors.Cut.UHD.BD66.Blu-ray")]
[TestCase("Movie.2009.Blu.ray.AVC.DTS.HD.MA.5.1")] [TestCase("Movie.2009.Blu.ray.AVC.DTS.HD.MA.5.1")]
[TestCase("[BD]Movie.Title.2008.2023.1080p.COMPLETE.BLURAY-RlsGrp")]
public void should_parse_brdisk_1080p_quality(string title) public void should_parse_brdisk_1080p_quality(string title)
{ {
ParseAndVerifyQuality(title, QualitySource.BLURAY, false, Resolution.R1080p, Modifier.BRDISK); ParseAndVerifyQuality(title, QualitySource.BLURAY, false, Resolution.R1080p, Modifier.BRDISK);

View File

@@ -5,7 +5,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Dapper" Version="2.0.143" /> <PackageReference Include="Dapper" Version="2.0.143" />
<PackageReference Include="NBuilder" Version="6.1.0" /> <PackageReference Include="NBuilder" Version="6.1.0" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" /> <PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.118-22" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\NzbDrone.Test.Common\Radarr.Test.Common.csproj" /> <ProjectReference Include="..\NzbDrone.Test.Common\Radarr.Test.Common.csproj" />

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Linq; using System.Linq;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
@@ -30,7 +30,7 @@ namespace NzbDrone.Core.Analytics
{ {
get get
{ {
var lastRecord = _historyService.Paged(new PagingSpec<MovieHistory>() { Page = 0, PageSize = 1, SortKey = "date", SortDirection = SortDirection.Descending }, null, null); var lastRecord = _historyService.Paged(new PagingSpec<MovieHistory>() { Page = 0, PageSize = 1, SortKey = "date", SortDirection = SortDirection.Descending });
var monthAgo = DateTime.UtcNow.AddMonths(-1); var monthAgo = DateTime.UtcNow.AddMonths(-1);
return lastRecord.Records.Any(v => v.Date > monthAgo); return lastRecord.Records.Any(v => v.Date > monthAgo);

View File

@@ -66,8 +66,7 @@ namespace NzbDrone.Core.Annotations
OAuth, OAuth,
Device, Device,
TagSelect, TagSelect,
RootFolder, RootFolder
QualityProfile
} }
public enum HiddenType public enum HiddenType

View File

@@ -1,36 +0,0 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Movies;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.AutoTagging.Specifications
{
public class QualityProfileSpecificationValidator : AbstractValidator<QualityProfileSpecification>
{
public QualityProfileSpecificationValidator()
{
RuleFor(c => c.Value).GreaterThan(0);
}
}
public class QualityProfileSpecification : AutoTaggingSpecificationBase
{
private static readonly QualityProfileSpecificationValidator Validator = new ();
public override int Order => 1;
public override string ImplementationName => "Quality Profile";
[FieldDefinition(1, Label = "Quality Profile", Type = FieldType.QualityProfile)]
public int Value { get; set; }
protected override bool IsSatisfiedByWithoutNegate(Movie movie)
{
return Value == movie.QualityProfileId;
}
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -190,13 +190,6 @@ namespace NzbDrone.Core.Configuration
set { SetValue("AutoRedownloadFailed", value); } set { SetValue("AutoRedownloadFailed", value); }
} }
public bool AutoRedownloadFailedFromInteractiveSearch
{
get { return GetValueBoolean("AutoRedownloadFailedFromInteractiveSearch", true); }
set { SetValue("AutoRedownloadFailedFromInteractiveSearch", value); }
}
public bool CreateEmptyMovieFolders public bool CreateEmptyMovieFolders
{ {
get { return GetValueBoolean("CreateEmptyMovieFolders", false); } get { return GetValueBoolean("CreateEmptyMovieFolders", false); }

View File

@@ -22,7 +22,6 @@ namespace NzbDrone.Core.Configuration
bool EnableCompletedDownloadHandling { get; set; } bool EnableCompletedDownloadHandling { get; set; }
bool AutoRedownloadFailed { get; set; } bool AutoRedownloadFailed { get; set; }
bool AutoRedownloadFailedFromInteractiveSearch { get; set; }
// Media Management // Media Management
bool AutoUnmonitorPreviouslyDownloadedMovies { get; set; } bool AutoUnmonitorPreviouslyDownloadedMovies { get; set; }

Some files were not shown because too many files have changed in this diff Show More