mirror of
https://github.com/Radarr/Radarr.git
synced 2026-04-17 21:26:22 -04:00
Compare commits
3 Commits
v5.2.1.827
...
collection
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04b15f2178 | ||
|
|
39d7320a75 | ||
|
|
207a4b19dc |
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[](https://dev.azure.com/Radarr/Radarr/_build/latest?definitionId=1&branchName=develop)
|
[](https://dev.azure.com/Radarr/Radarr/_build/latest?definitionId=1&branchName=develop)
|
||||||
[](https://translate.servarr.com/engage/radarr/?utm_source=widget)
|
[](https://translate.servarr.com/engage/radarr/?utm_source=widget)
|
||||||
[](https://wiki.servarr.com/radarr/installation/docker)
|
[](https://wiki.servarr.com/radarr/installation#docker)
|
||||||

|

|
||||||
[](#backers)
|
[](#backers)
|
||||||
[](#sponsors)
|
[](#sponsors)
|
||||||
|
|||||||
@@ -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)'
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import { tooltipPositions } from 'Helpers/Props';
|
import { tooltipPositions } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
import QueueStatus from './QueueStatus';
|
import QueueStatus from './QueueStatus';
|
||||||
import styles from './QueueStatusCell.css';
|
import styles from './QueueStatusCell.css';
|
||||||
|
|
||||||
@@ -40,8 +41,8 @@ QueueStatusCell.propTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
QueueStatusCell.defaultProps = {
|
QueueStatusCell.defaultProps = {
|
||||||
trackedDownloadStatus: 'ok',
|
trackedDownloadStatus: translate('Ok'),
|
||||||
trackedDownloadState: 'downloading'
|
trackedDownloadState: translate('Downloading')
|
||||||
};
|
};
|
||||||
|
|
||||||
export default QueueStatusCell;
|
export default QueueStatusCell;
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Icon from 'Components/Icon';
|
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
|
||||||
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
|
||||||
import formatTime from 'Utilities/Date/formatTime';
|
import formatTime from 'Utilities/Date/formatTime';
|
||||||
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
|
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
|
||||||
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
||||||
@@ -28,13 +25,11 @@ function TimeleftCell(props) {
|
|||||||
const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true });
|
const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRowCell className={styles.timeleft}>
|
<TableRowCell
|
||||||
<Tooltip
|
className={styles.timeleft}
|
||||||
anchor={<Icon name={icons.INFO} />}
|
title={translate('DelayingDownloadUntil', { date, time })}
|
||||||
tooltip={translate('DelayingDownloadUntil', { date, time })}
|
>
|
||||||
kind={kinds.INVERSE}
|
-
|
||||||
position={tooltipPositions.TOP}
|
|
||||||
/>
|
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -44,13 +39,11 @@ function TimeleftCell(props) {
|
|||||||
const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true });
|
const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRowCell className={styles.timeleft}>
|
<TableRowCell
|
||||||
<Tooltip
|
className={styles.timeleft}
|
||||||
anchor={<Icon name={icons.INFO} />}
|
title={translate('RetryingDownloadOn', { date, time })}
|
||||||
tooltip={translate('RetryingDownloadOn', { date, time })}
|
>
|
||||||
kind={kinds.INVERSE}
|
-
|
||||||
position={tooltipPositions.TOP}
|
|
||||||
/>
|
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import Alert from 'Components/Alert';
|
|
||||||
import TextInput from 'Components/Form/TextInput';
|
import TextInput from 'Components/Form/TextInput';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import Button from 'Components/Link/Button';
|
import Button from 'Components/Link/Button';
|
||||||
@@ -131,12 +130,7 @@ class AddNewMovie extends Component {
|
|||||||
<div className={styles.helpText}>
|
<div className={styles.helpText}>
|
||||||
{translate('FailedLoadingSearchResults')}
|
{translate('FailedLoadingSearchResults')}
|
||||||
</div>
|
</div>
|
||||||
<Alert kind={kinds.WARNING}>{getErrorMessage(error)}</Alert>
|
<div>{getErrorMessage(error)}</div>
|
||||||
<div>
|
|
||||||
<Link to="https://wiki.servarr.com/radarr/troubleshooting#invalid-response-received-from-tmdb">
|
|
||||||
{translate('WhySearchesCouldBeFailing')}
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div> : null
|
</div> : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}>
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
67
frontend/src/Movie/Details/MovieReleaseDates.js
Normal file
67
frontend/src/Movie/Details/MovieReleaseDates.js
Normal 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;
|
||||||
@@ -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;
|
|
||||||
20
frontend/src/Movie/Details/MovieReleaseDatesConnector.js
Normal file
20
frontend/src/Movie/Details/MovieReleaseDatesConnector.js
Normal 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);
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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}>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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}>
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ export const defaultState = {
|
|||||||
|
|
||||||
selectedFilterKey: 'monitored',
|
selectedFilterKey: 'monitored',
|
||||||
|
|
||||||
|
customFilters: [],
|
||||||
|
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
key: 'all',
|
key: 'all',
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
),
|
),
|
||||||
|
|||||||
17
frontend/src/Store/Selectors/createCollectionSelector.js
Normal file
17
frontend/src/Store/Selectors/createCollectionSelector.js
Normal 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;
|
||||||
@@ -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;
|
|
||||||
@@ -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',
|
||||||
|
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -146,9 +146,7 @@ class RestoreBackupModalContent extends Component {
|
|||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
{
|
{
|
||||||
!!id && translate('WouldYouLikeToRestoreBackup', {
|
!!id && translate('WouldYouLikeToRestoreBackup', { name })
|
||||||
name
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -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")]
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -66,8 +66,7 @@ namespace NzbDrone.Core.Annotations
|
|||||||
OAuth,
|
OAuth,
|
||||||
Device,
|
Device,
|
||||||
TagSelect,
|
TagSelect,
|
||||||
RootFolder,
|
RootFolder
|
||||||
QualityProfile
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum HiddenType
|
public enum HiddenType
|
||||||
|
|||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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); }
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user