mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-06 13:31:28 -05:00
Compare commits
13 Commits
v5.3.5.859
...
zeus
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d63b3ca59d | ||
|
|
c8faab9928 | ||
|
|
0a6b0ee959 | ||
|
|
b993f70d2c | ||
|
|
85544ca8f6 | ||
|
|
9e7ad678b0 | ||
|
|
0aebd90ac9 | ||
|
|
76bed80060 | ||
|
|
669b50dc72 | ||
|
|
18fc1413c3 | ||
|
|
775b1ba9cf | ||
|
|
5ad3f96e0f | ||
|
|
b024fcf5ee |
@@ -2,7 +2,7 @@
|
||||
|
||||
[](https://dev.azure.com/Radarr/Radarr/_build/latest?definitionId=1&branchName=develop)
|
||||
[](https://translate.servarr.com/engage/radarr/?utm_source=widget)
|
||||
[](https://wiki.servarr.com/radarr/installation/docker)
|
||||
[](https://wiki.servarr.com/radarr/installation#docker)
|
||||

|
||||
[](#backers)
|
||||
[](#sponsors)
|
||||
|
||||
@@ -9,24 +9,25 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '5.3.5'
|
||||
majorVersion: '6.0.1'
|
||||
minorVersion: $[counter('minorVersion', 2000)]
|
||||
radarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
||||
sentryOrg: 'servarr'
|
||||
sentryUrl: 'https://sentry.servarr.com'
|
||||
dotnetVersion: '6.0.417'
|
||||
dotnetVersion: '6.0.413'
|
||||
nodeVersion: '16.X'
|
||||
innoVersion: '6.2.2'
|
||||
innoVersion: '6.2.0'
|
||||
windowsImage: 'windows-2022'
|
||||
linuxImage: 'ubuntu-20.04'
|
||||
macImage: 'macOS-11'
|
||||
macImage: 'macOS-13'
|
||||
|
||||
trigger:
|
||||
branches:
|
||||
include:
|
||||
- develop
|
||||
- master
|
||||
- zeus
|
||||
paths:
|
||||
exclude:
|
||||
- .github
|
||||
@@ -1242,7 +1243,6 @@ stages:
|
||||
- stage: Report_Out
|
||||
dependsOn:
|
||||
- Analyze
|
||||
- Installer
|
||||
- Unit_Test
|
||||
- Integration
|
||||
- Automation
|
||||
|
||||
2
build.sh
2
build.sh
@@ -254,7 +254,7 @@ InstallInno()
|
||||
ProgressStart "Installing portable Inno Setup"
|
||||
|
||||
rm -rf _inno
|
||||
curl -s --output innosetup.exe "https://files.jrsoftware.org/is/6/innosetup-${INNOVERSION:-6.2.2}.exe"
|
||||
curl -s --output innosetup.exe "https://files.jrsoftware.org/is/6/innosetup-${INNOVERSION:-6.2.0}.exe"
|
||||
mkdir _inno
|
||||
./innosetup.exe //portable=1 //silent //currentuser //dir=.\\_inno
|
||||
rm innosetup.exe
|
||||
|
||||
@@ -2,8 +2,6 @@ const loose = true;
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
'@babel/plugin-transform-logical-assignment-operators',
|
||||
|
||||
// Stage 1
|
||||
'@babel/plugin-proposal-export-default-from',
|
||||
['@babel/plugin-transform-optional-chaining', { loose }],
|
||||
|
||||
@@ -36,7 +36,6 @@ class Blocklist extends Component {
|
||||
lastToggled: null,
|
||||
selectedState: {},
|
||||
isConfirmRemoveModalOpen: false,
|
||||
isConfirmClearModalOpen: false,
|
||||
items: props.items
|
||||
};
|
||||
}
|
||||
@@ -91,19 +90,6 @@ class Blocklist extends Component {
|
||||
this.setState({ isConfirmRemoveModalOpen: false });
|
||||
};
|
||||
|
||||
onClearBlocklistPress = () => {
|
||||
this.setState({ isConfirmClearModalOpen: true });
|
||||
};
|
||||
|
||||
onClearBlocklistConfirmed = () => {
|
||||
this.props.onClearBlocklistPress();
|
||||
this.setState({ isConfirmClearModalOpen: false });
|
||||
};
|
||||
|
||||
onConfirmClearModalClose = () => {
|
||||
this.setState({ isConfirmClearModalOpen: false });
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
@@ -117,6 +103,7 @@ class Blocklist extends Component {
|
||||
totalRecords,
|
||||
isRemoving,
|
||||
isClearingBlocklistExecuting,
|
||||
onClearBlocklistPress,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
@@ -124,8 +111,7 @@ class Blocklist extends Component {
|
||||
allSelected,
|
||||
allUnselected,
|
||||
selectedState,
|
||||
isConfirmRemoveModalOpen,
|
||||
isConfirmClearModalOpen
|
||||
isConfirmRemoveModalOpen
|
||||
} = this.state;
|
||||
|
||||
const selectedIds = this.getSelectedIds();
|
||||
@@ -145,9 +131,8 @@ class Blocklist extends Component {
|
||||
<PageToolbarButton
|
||||
label={translate('Clear')}
|
||||
iconName={icons.CLEAR}
|
||||
isDisabled={!items.length}
|
||||
isSpinning={isClearingBlocklistExecuting}
|
||||
onPress={this.onClearBlocklistPress}
|
||||
onPress={onClearBlocklistPress}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
|
||||
@@ -230,16 +215,6 @@ class Blocklist extends Component {
|
||||
onConfirm={this.onRemoveSelectedConfirmed}
|
||||
onCancel={this.onConfirmRemoveModalClose}
|
||||
/>
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={isConfirmClearModalOpen}
|
||||
kind={kinds.DANGER}
|
||||
title={translate('ClearBlocklist')}
|
||||
message={translate('ClearBlocklistMessageText')}
|
||||
confirmLabel={translate('Clear')}
|
||||
onConfirm={this.onClearBlocklistConfirmed}
|
||||
onCancel={this.onConfirmClearModalClose}
|
||||
/>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptions
|
||||
import TablePager from 'Components/Table/TablePager';
|
||||
import { align, icons, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import HistoryFilterModal from './HistoryFilterModal';
|
||||
import HistoryRowConnector from './HistoryRowConnector';
|
||||
|
||||
class History extends Component {
|
||||
@@ -34,7 +33,6 @@ class History extends Component {
|
||||
columns,
|
||||
selectedFilterKey,
|
||||
filters,
|
||||
customFilters,
|
||||
totalRecords,
|
||||
onFilterSelect,
|
||||
onFirstPagePress,
|
||||
@@ -72,8 +70,7 @@ class History extends Component {
|
||||
alignMenu={align.RIGHT}
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
filters={filters}
|
||||
customFilters={customFilters}
|
||||
filterModalConnectorComponent={HistoryFilterModal}
|
||||
customFilters={[]}
|
||||
onFilterSelect={onFilterSelect}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
@@ -147,9 +144,8 @@ History.propTypes = {
|
||||
moviesError: PropTypes.object,
|
||||
items: 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,
|
||||
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
totalRecords: PropTypes.number,
|
||||
onFilterSelect: PropTypes.func.isRequired,
|
||||
onFirstPagePress: PropTypes.func.isRequired
|
||||
|
||||
@@ -4,7 +4,6 @@ import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import withCurrentPage from 'Components/withCurrentPage';
|
||||
import * as historyActions from 'Store/Actions/historyActions';
|
||||
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
||||
import History from './History';
|
||||
|
||||
@@ -12,13 +11,11 @@ function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.history,
|
||||
(state) => state.movies,
|
||||
createCustomFiltersSelector('history'),
|
||||
(history, movies, customFilters) => {
|
||||
(history, movies) => {
|
||||
return {
|
||||
isMoviesFetching: movies.isFetching,
|
||||
isMoviesPopulated: movies.isPopulated,
|
||||
moviesError: movies.error,
|
||||
customFilters,
|
||||
...history
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { icons, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './HistoryEventTypeCell.css';
|
||||
|
||||
function getIconName(eventType, data) {
|
||||
function getIconName(eventType) {
|
||||
switch (eventType) {
|
||||
case 'grabbed':
|
||||
return icons.DOWNLOADING;
|
||||
@@ -17,7 +17,7 @@ function getIconName(eventType, data) {
|
||||
case 'downloadFailed':
|
||||
return icons.DOWNLOADING;
|
||||
case 'movieFileDeleted':
|
||||
return data.reason === 'MissingFromDisk' ? icons.FILE_MISSING : icons.DELETE;
|
||||
return icons.DELETE;
|
||||
case 'movieFileRenamed':
|
||||
return icons.ORGANIZE;
|
||||
case 'downloadIgnored':
|
||||
@@ -47,7 +47,7 @@ function getTooltip(eventType, data) {
|
||||
case 'downloadFailed':
|
||||
return translate('MovieDownloadFailedTooltip');
|
||||
case 'movieFileDeleted':
|
||||
return data.reason === 'MissingFromDisk' ? translate('MovieFileMissingTooltip') : translate('MovieFileDeletedTooltip');
|
||||
return translate('MovieFileDeletedTooltip');
|
||||
case 'movieFileRenamed':
|
||||
return translate('MovieFileRenamedTooltip');
|
||||
case 'downloadIgnored':
|
||||
@@ -58,7 +58,7 @@ function getTooltip(eventType, data) {
|
||||
}
|
||||
|
||||
function HistoryEventTypeCell({ eventType, data }) {
|
||||
const iconName = getIconName(eventType, data);
|
||||
const iconName = getIconName(eventType);
|
||||
const iconKind = getIconKind(eventType);
|
||||
const tooltip = getTooltip(eventType, data);
|
||||
|
||||
|
||||
@@ -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 Alert from 'Components/Alert';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||
@@ -22,10 +21,9 @@ import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||
import removeOldSelectedState from 'Utilities/Table/removeOldSelectedState';
|
||||
import selectAll from 'Utilities/Table/selectAll';
|
||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||
import QueueFilterModal from './QueueFilterModal';
|
||||
import QueueOptionsConnector from './QueueOptionsConnector';
|
||||
import QueueRowConnector from './QueueRowConnector';
|
||||
import RemoveQueueItemModal from './RemoveQueueItemModal';
|
||||
import RemoveQueueItemsModal from './RemoveQueueItemsModal';
|
||||
|
||||
class Queue extends Component {
|
||||
|
||||
@@ -155,16 +153,11 @@ class Queue extends Component {
|
||||
isMoviesPopulated,
|
||||
moviesError,
|
||||
columns,
|
||||
selectedFilterKey,
|
||||
filters,
|
||||
customFilters,
|
||||
count,
|
||||
totalRecords,
|
||||
isGrabbing,
|
||||
isRemoving,
|
||||
isRefreshMonitoredDownloadsExecuting,
|
||||
onRefreshPress,
|
||||
onFilterSelect,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
@@ -227,15 +220,6 @@ class Queue extends Component {
|
||||
iconName={icons.TABLE}
|
||||
/>
|
||||
</TableOptionsModalWrapper>
|
||||
|
||||
<FilterMenu
|
||||
alignMenu={align.RIGHT}
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
filters={filters}
|
||||
customFilters={customFilters}
|
||||
filterModalConnectorComponent={QueueFilterModal}
|
||||
onFilterSelect={onFilterSelect}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
</PageToolbar>
|
||||
|
||||
@@ -257,11 +241,7 @@ class Queue extends Component {
|
||||
{
|
||||
isAllPopulated && !hasError && !items.length ?
|
||||
<Alert kind={kinds.INFO}>
|
||||
{
|
||||
selectedFilterKey !== 'all' && count > 0 ?
|
||||
translate('QueueFilterHasNoItems') :
|
||||
translate('QueueIsEmpty')
|
||||
}
|
||||
{translate('QueueIsEmpty')}
|
||||
</Alert> :
|
||||
null
|
||||
}
|
||||
@@ -307,16 +287,9 @@ class Queue extends Component {
|
||||
}
|
||||
</PageContentBody>
|
||||
|
||||
<RemoveQueueItemModal
|
||||
<RemoveQueueItemsModal
|
||||
isOpen={isConfirmRemoveModalOpen}
|
||||
selectedCount={selectedCount}
|
||||
canChangeCategory={isConfirmRemoveModalOpen && (
|
||||
selectedIds.every((id) => {
|
||||
const item = items.find((i) => i.id === id);
|
||||
|
||||
return !!(item && item.downloadClientHasPostImportCategory);
|
||||
})
|
||||
)}
|
||||
canIgnore={isConfirmRemoveModalOpen && (
|
||||
selectedIds.every((id) => {
|
||||
const item = items.find((i) => i.id === id);
|
||||
@@ -324,7 +297,7 @@ class Queue extends Component {
|
||||
return !!(item && item.movieId);
|
||||
})
|
||||
)}
|
||||
pending={isConfirmRemoveModalOpen && (
|
||||
allPending={isConfirmRemoveModalOpen && (
|
||||
selectedIds.every((id) => {
|
||||
const item = items.find((i) => i.id === id);
|
||||
|
||||
@@ -352,22 +325,13 @@ Queue.propTypes = {
|
||||
moviesError: PropTypes.object,
|
||||
items: 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,
|
||||
isGrabbing: PropTypes.bool.isRequired,
|
||||
isRemoving: PropTypes.bool.isRequired,
|
||||
isRefreshMonitoredDownloadsExecuting: PropTypes.bool.isRequired,
|
||||
onRefreshPress: PropTypes.func.isRequired,
|
||||
onGrabSelectedPress: PropTypes.func.isRequired,
|
||||
onRemoveSelectedPress: PropTypes.func.isRequired,
|
||||
onFilterSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
Queue.defaultProps = {
|
||||
count: 0
|
||||
onRemoveSelectedPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default Queue;
|
||||
|
||||
@@ -6,7 +6,6 @@ import * as commandNames from 'Commands/commandNames';
|
||||
import withCurrentPage from 'Components/withCurrentPage';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import * as queueActions from 'Store/Actions/queueActions';
|
||||
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
||||
import Queue from './Queue';
|
||||
@@ -16,16 +15,12 @@ function createMapStateToProps() {
|
||||
(state) => state.movies,
|
||||
(state) => state.queue.options,
|
||||
(state) => state.queue.paged,
|
||||
(state) => state.queue.status.item,
|
||||
createCustomFiltersSelector('queue'),
|
||||
createCommandExecutingSelector(commandNames.REFRESH_MONITORED_DOWNLOADS),
|
||||
(movies, options, queue, status, customFilters, isRefreshMonitoredDownloadsExecuting) => {
|
||||
(movies, options, queue, isRefreshMonitoredDownloadsExecuting) => {
|
||||
return {
|
||||
count: options.includeUnknownMovieItems ? status.totalCount : status.count,
|
||||
isMoviesFetching: movies.isFetching,
|
||||
isMoviesPopulated: movies.isPopulated,
|
||||
moviesError: movies.error,
|
||||
customFilters,
|
||||
isRefreshMonitoredDownloadsExecuting,
|
||||
...options,
|
||||
...queue
|
||||
@@ -111,10 +106,6 @@ class QueueConnector extends Component {
|
||||
this.props.setQueueSort({ sortKey });
|
||||
};
|
||||
|
||||
onFilterSelect = (selectedFilterKey) => {
|
||||
this.props.setQueueFilter({ selectedFilterKey });
|
||||
};
|
||||
|
||||
onTableOptionChange = (payload) => {
|
||||
this.props.setQueueTableOption(payload);
|
||||
|
||||
@@ -149,7 +140,6 @@ class QueueConnector extends Component {
|
||||
onLastPagePress={this.onLastPagePress}
|
||||
onPageSelect={this.onPageSelect}
|
||||
onSortPress={this.onSortPress}
|
||||
onFilterSelect={this.onFilterSelect}
|
||||
onTableOptionChange={this.onTableOptionChange}
|
||||
onRefreshPress={this.onRefreshPress}
|
||||
onGrabSelectedPress={this.onGrabSelectedPress}
|
||||
@@ -172,7 +162,6 @@ QueueConnector.propTypes = {
|
||||
gotoQueueLastPage: PropTypes.func.isRequired,
|
||||
gotoQueuePage: PropTypes.func.isRequired,
|
||||
setQueueSort: PropTypes.func.isRequired,
|
||||
setQueueFilter: PropTypes.func.isRequired,
|
||||
setQueueTableOption: PropTypes.func.isRequired,
|
||||
clearQueue: PropTypes.func.isRequired,
|
||||
grabQueueItems: PropTypes.func.isRequired,
|
||||
|
||||
@@ -81,9 +81,4 @@ QueueDetails.propTypes = {
|
||||
progressBar: PropTypes.node.isRequired
|
||||
};
|
||||
|
||||
QueueDetails.defaultProps = {
|
||||
trackedDownloadStatus: 'ok',
|
||||
trackedDownloadState: 'downloading'
|
||||
};
|
||||
|
||||
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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||
import ProgressBar from 'Components/ProgressBar';
|
||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||
// import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
@@ -96,9 +96,7 @@ class QueueRow extends Component {
|
||||
indexer,
|
||||
outputPath,
|
||||
downloadClient,
|
||||
downloadClientHasPostImportCategory,
|
||||
estimatedCompletionTime,
|
||||
added,
|
||||
timeleft,
|
||||
size,
|
||||
sizeleft,
|
||||
@@ -317,15 +315,6 @@ class QueueRow extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'added') {
|
||||
return (
|
||||
<RelativeDateCellConnector
|
||||
key={name}
|
||||
date={added}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'actions') {
|
||||
return (
|
||||
<TableRowCell
|
||||
@@ -374,7 +363,6 @@ class QueueRow extends Component {
|
||||
<RemoveQueueItemModal
|
||||
isOpen={isRemoveQueueItemModalOpen}
|
||||
sourceTitle={title}
|
||||
canChangeCategory={!!downloadClientHasPostImportCategory}
|
||||
canIgnore={!!movie}
|
||||
isPending={isPending}
|
||||
onRemovePress={this.onRemoveQueueItemModalConfirmed}
|
||||
@@ -404,9 +392,7 @@ QueueRow.propTypes = {
|
||||
indexer: PropTypes.string,
|
||||
outputPath: PropTypes.string,
|
||||
downloadClient: PropTypes.string,
|
||||
downloadClientHasPostImportCategory: PropTypes.bool,
|
||||
estimatedCompletionTime: PropTypes.string,
|
||||
added: PropTypes.string,
|
||||
timeleft: PropTypes.string,
|
||||
size: PropTypes.number,
|
||||
year: PropTypes.number,
|
||||
|
||||
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import { tooltipPositions } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import QueueStatus from './QueueStatus';
|
||||
import styles from './QueueStatusCell.css';
|
||||
|
||||
@@ -40,8 +41,8 @@ QueueStatusCell.propTypes = {
|
||||
};
|
||||
|
||||
QueueStatusCell.defaultProps = {
|
||||
trackedDownloadStatus: 'ok',
|
||||
trackedDownloadState: 'downloading'
|
||||
trackedDownloadStatus: translate('Ok'),
|
||||
trackedDownloadState: translate('Downloading')
|
||||
};
|
||||
|
||||
export default QueueStatusCell;
|
||||
|
||||
171
frontend/src/Activity/Queue/RemoveQueueItemModal.js
Normal file
171
frontend/src/Activity/Queue/RemoveQueueItemModal.js
Normal file
@@ -0,0 +1,171 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import Button from 'Components/Link/Button';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
class RemoveQueueItemModal extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
remove: true,
|
||||
blocklist: false,
|
||||
skipRedownload: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
resetState = function() {
|
||||
this.setState({
|
||||
remove: true,
|
||||
blocklist: false,
|
||||
skipRedownload: false
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onRemoveChange = ({ value }) => {
|
||||
this.setState({ remove: value });
|
||||
};
|
||||
|
||||
onBlocklistChange = ({ value }) => {
|
||||
this.setState({ blocklist: value });
|
||||
};
|
||||
|
||||
onSkipRedownloadChange = ({ value }) => {
|
||||
this.setState({ skipRedownload: value });
|
||||
};
|
||||
|
||||
onRemoveConfirmed = () => {
|
||||
const state = this.state;
|
||||
|
||||
this.resetState();
|
||||
this.props.onRemovePress(state);
|
||||
};
|
||||
|
||||
onModalClose = () => {
|
||||
this.resetState();
|
||||
this.props.onModalClose();
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
isOpen,
|
||||
sourceTitle,
|
||||
canIgnore,
|
||||
isPending
|
||||
} = this.props;
|
||||
|
||||
const { remove, blocklist, skipRedownload } = this.state;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
size={sizes.MEDIUM}
|
||||
onModalClose={this.onModalClose}
|
||||
>
|
||||
<ModalContent
|
||||
onModalClose={this.onModalClose}
|
||||
>
|
||||
<ModalHeader>
|
||||
{translate('RemoveQueueItem', { sourceTitle })}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<div>
|
||||
{translate('RemoveQueueItemConfirmation', { sourceTitle })}
|
||||
</div>
|
||||
|
||||
{
|
||||
isPending ?
|
||||
null :
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="remove"
|
||||
value={remove}
|
||||
helpTextWarning={translate('RemoveFromDownloadClientHelpTextWarning')}
|
||||
isDisabled={!canIgnore}
|
||||
onChange={this.onRemoveChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
}
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('BlocklistRelease')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="blocklist"
|
||||
value={blocklist}
|
||||
helpText={translate('BlocklistReleaseHelpText')}
|
||||
onChange={this.onBlocklistChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{
|
||||
blocklist ?
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('SkipRedownload')}</FormLabel>
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="skipRedownload"
|
||||
value={skipRedownload}
|
||||
helpText={translate('SkipRedownloadHelpText')}
|
||||
onChange={this.onSkipRedownloadChange}
|
||||
/>
|
||||
</FormGroup> :
|
||||
null
|
||||
}
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={this.onModalClose}>
|
||||
{translate('Close')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
kind={kinds.DANGER}
|
||||
onPress={this.onRemoveConfirmed}
|
||||
>
|
||||
{translate('Remove')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RemoveQueueItemModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
sourceTitle: PropTypes.string.isRequired,
|
||||
canIgnore: PropTypes.bool.isRequired,
|
||||
isPending: PropTypes.bool.isRequired,
|
||||
onRemovePress: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default RemoveQueueItemModal;
|
||||
@@ -1,230 +0,0 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import Button from 'Components/Link/Button';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './RemoveQueueItemModal.css';
|
||||
|
||||
interface RemovePressProps {
|
||||
remove: boolean;
|
||||
changeCategory: boolean;
|
||||
blocklist: boolean;
|
||||
skipRedownload: boolean;
|
||||
}
|
||||
|
||||
interface RemoveQueueItemModalProps {
|
||||
isOpen: boolean;
|
||||
sourceTitle: string;
|
||||
canChangeCategory: boolean;
|
||||
canIgnore: boolean;
|
||||
isPending: boolean;
|
||||
selectedCount?: number;
|
||||
onRemovePress(props: RemovePressProps): void;
|
||||
onModalClose: () => void;
|
||||
}
|
||||
|
||||
type RemovalMethod = 'removeFromClient' | 'changeCategory' | 'ignore';
|
||||
type BlocklistMethod =
|
||||
| 'doNotBlocklist'
|
||||
| 'blocklistAndSearch'
|
||||
| 'blocklistOnly';
|
||||
|
||||
function RemoveQueueItemModal(props: RemoveQueueItemModalProps) {
|
||||
const {
|
||||
isOpen,
|
||||
sourceTitle,
|
||||
canIgnore,
|
||||
canChangeCategory,
|
||||
isPending,
|
||||
selectedCount,
|
||||
onRemovePress,
|
||||
onModalClose,
|
||||
} = props;
|
||||
|
||||
const multipleSelected = selectedCount && selectedCount > 1;
|
||||
|
||||
const [removalMethod, setRemovalMethod] =
|
||||
useState<RemovalMethod>('removeFromClient');
|
||||
const [blocklistMethod, setBlocklistMethod] =
|
||||
useState<BlocklistMethod>('doNotBlocklist');
|
||||
|
||||
const { title, message } = useMemo(() => {
|
||||
if (!selectedCount) {
|
||||
return {
|
||||
title: translate('RemoveQueueItem', { sourceTitle }),
|
||||
message: translate('RemoveQueueItemConfirmation', { sourceTitle }),
|
||||
};
|
||||
}
|
||||
|
||||
if (selectedCount === 1) {
|
||||
return {
|
||||
title: translate('RemoveSelectedItem'),
|
||||
message: translate('RemoveSelectedItemQueueMessageText'),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
title: translate('RemoveSelectedItems'),
|
||||
message: translate('RemoveSelectedItemsQueueMessageText', {
|
||||
selectedCount,
|
||||
}),
|
||||
};
|
||||
}, [sourceTitle, selectedCount]);
|
||||
|
||||
const removalMethodOptions = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
key: 'removeFromClient',
|
||||
value: translate('RemoveFromDownloadClient'),
|
||||
hint: multipleSelected
|
||||
? translate('RemoveMultipleFromDownloadClientHint')
|
||||
: translate('RemoveFromDownloadClientHint'),
|
||||
},
|
||||
{
|
||||
key: 'changeCategory',
|
||||
value: translate('ChangeCategory'),
|
||||
isDisabled: !canChangeCategory,
|
||||
hint: multipleSelected
|
||||
? translate('ChangeCategoryMultipleHint')
|
||||
: translate('ChangeCategoryHint'),
|
||||
},
|
||||
{
|
||||
key: 'ignore',
|
||||
value: multipleSelected
|
||||
? translate('IgnoreDownloads')
|
||||
: translate('IgnoreDownload'),
|
||||
isDisabled: !canIgnore,
|
||||
hint: multipleSelected
|
||||
? translate('IgnoreDownloadsHint')
|
||||
: translate('IgnoreDownloadHint'),
|
||||
},
|
||||
];
|
||||
}, [canChangeCategory, canIgnore, multipleSelected]);
|
||||
|
||||
const blocklistMethodOptions = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
key: 'doNotBlocklist',
|
||||
value: translate('DoNotBlocklist'),
|
||||
hint: translate('DoNotBlocklistHint'),
|
||||
},
|
||||
{
|
||||
key: 'blocklistAndSearch',
|
||||
value: translate('BlocklistAndSearch'),
|
||||
hint: multipleSelected
|
||||
? translate('BlocklistAndSearchMultipleHint')
|
||||
: translate('BlocklistAndSearchHint'),
|
||||
},
|
||||
{
|
||||
key: 'blocklistOnly',
|
||||
value: translate('BlocklistOnly'),
|
||||
hint: multipleSelected
|
||||
? translate('BlocklistMultipleOnlyHint')
|
||||
: translate('BlocklistOnlyHint'),
|
||||
},
|
||||
];
|
||||
}, [multipleSelected]);
|
||||
|
||||
const handleRemovalMethodChange = useCallback(
|
||||
({ value }: { value: RemovalMethod }) => {
|
||||
setRemovalMethod(value);
|
||||
},
|
||||
[setRemovalMethod]
|
||||
);
|
||||
|
||||
const handleBlocklistMethodChange = useCallback(
|
||||
({ value }: { value: BlocklistMethod }) => {
|
||||
setBlocklistMethod(value);
|
||||
},
|
||||
[setBlocklistMethod]
|
||||
);
|
||||
|
||||
const handleConfirmRemove = useCallback(() => {
|
||||
onRemovePress({
|
||||
remove: removalMethod === 'removeFromClient',
|
||||
changeCategory: removalMethod === 'changeCategory',
|
||||
blocklist: blocklistMethod !== 'doNotBlocklist',
|
||||
skipRedownload: blocklistMethod === 'blocklistOnly',
|
||||
});
|
||||
|
||||
setRemovalMethod('removeFromClient');
|
||||
setBlocklistMethod('doNotBlocklist');
|
||||
}, [
|
||||
removalMethod,
|
||||
blocklistMethod,
|
||||
setRemovalMethod,
|
||||
setBlocklistMethod,
|
||||
onRemovePress,
|
||||
]);
|
||||
|
||||
const handleModalClose = useCallback(() => {
|
||||
setRemovalMethod('removeFromClient');
|
||||
setBlocklistMethod('doNotBlocklist');
|
||||
|
||||
onModalClose();
|
||||
}, [setRemovalMethod, setBlocklistMethod, onModalClose]);
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} size={sizes.MEDIUM} onModalClose={handleModalClose}>
|
||||
<ModalContent onModalClose={handleModalClose}>
|
||||
<ModalHeader>{title}</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<div className={styles.message}>{message}</div>
|
||||
|
||||
{isPending ? null : (
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('RemoveQueueItemRemovalMethod')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name="removalMethod"
|
||||
value={removalMethod}
|
||||
values={removalMethodOptions}
|
||||
isDisabled={!canChangeCategory && !canIgnore}
|
||||
helpTextWarning={translate(
|
||||
'RemoveQueueItemRemovalMethodHelpTextWarning'
|
||||
)}
|
||||
onChange={handleRemovalMethodChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>
|
||||
{multipleSelected
|
||||
? translate('BlocklistReleases')
|
||||
: translate('BlocklistRelease')}
|
||||
</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name="blocklistMethod"
|
||||
value={blocklistMethod}
|
||||
values={blocklistMethodOptions}
|
||||
helpText={translate('BlocklistReleaseHelpText')}
|
||||
onChange={handleBlocklistMethodChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={handleModalClose}>{translate('Close')}</Button>
|
||||
|
||||
<Button kind={kinds.DANGER} onPress={handleConfirmRemove}>
|
||||
{translate('Remove')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default RemoveQueueItemModal;
|
||||
174
frontend/src/Activity/Queue/RemoveQueueItemsModal.js
Normal file
174
frontend/src/Activity/Queue/RemoveQueueItemsModal.js
Normal file
@@ -0,0 +1,174 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import Button from 'Components/Link/Button';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './RemoveQueueItemsModal.css';
|
||||
|
||||
class RemoveQueueItemsModal extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
remove: true,
|
||||
blocklist: false,
|
||||
skipRedownload: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
resetState = function() {
|
||||
this.setState({
|
||||
remove: true,
|
||||
blocklist: false,
|
||||
skipRedownload: false
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onRemoveChange = ({ value }) => {
|
||||
this.setState({ remove: value });
|
||||
};
|
||||
|
||||
onBlocklistChange = ({ value }) => {
|
||||
this.setState({ blocklist: value });
|
||||
};
|
||||
|
||||
onSkipRedownloadChange = ({ value }) => {
|
||||
this.setState({ skipRedownload: value });
|
||||
};
|
||||
|
||||
onRemoveConfirmed = () => {
|
||||
const state = this.state;
|
||||
|
||||
this.resetState();
|
||||
this.props.onRemovePress(state);
|
||||
};
|
||||
|
||||
onModalClose = () => {
|
||||
this.resetState();
|
||||
this.props.onModalClose();
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
isOpen,
|
||||
selectedCount,
|
||||
canIgnore,
|
||||
allPending
|
||||
} = this.props;
|
||||
|
||||
const { remove, blocklist, skipRedownload } = this.state;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
size={sizes.MEDIUM}
|
||||
onModalClose={this.onModalClose}
|
||||
>
|
||||
<ModalContent
|
||||
onModalClose={this.onModalClose}
|
||||
>
|
||||
<ModalHeader>
|
||||
{selectedCount > 1 ? translate('RemoveSelectedItems') : translate('RemoveSelectedItem')}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<div className={styles.message}>
|
||||
{selectedCount > 1 ? translate('RemoveSelectedItemsQueueMessageText', { selectedCount }) : translate('RemoveSelectedItemQueueMessageText')}
|
||||
</div>
|
||||
|
||||
{
|
||||
allPending ?
|
||||
null :
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="remove"
|
||||
value={remove}
|
||||
helpTextWarning={translate('RemoveHelpTextWarning')}
|
||||
isDisabled={!canIgnore}
|
||||
onChange={this.onRemoveChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
}
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>
|
||||
{selectedCount > 1 ? translate('BlocklistReleases') : translate('BlocklistRelease')}
|
||||
</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="blocklist"
|
||||
value={blocklist}
|
||||
helpText={translate('BlocklistReleaseHelpText')}
|
||||
onChange={this.onBlocklistChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{
|
||||
blocklist ?
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('SkipRedownload')}</FormLabel>
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="skipRedownload"
|
||||
value={skipRedownload}
|
||||
helpText={translate('SkipRedownloadHelpText')}
|
||||
onChange={this.onSkipRedownloadChange}
|
||||
/>
|
||||
</FormGroup> :
|
||||
null
|
||||
}
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={this.onModalClose}>
|
||||
{translate('Close')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
kind={kinds.DANGER}
|
||||
onPress={this.onRemoveConfirmed}
|
||||
>
|
||||
{translate('Remove')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RemoveQueueItemsModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
selectedCount: PropTypes.number.isRequired,
|
||||
canIgnore: PropTypes.bool.isRequired,
|
||||
allPending: PropTypes.bool.isRequired,
|
||||
onRemovePress: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default RemoveQueueItemsModal;
|
||||
@@ -1,9 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
||||
import formatTime from 'Utilities/Date/formatTime';
|
||||
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
|
||||
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
||||
@@ -28,13 +25,11 @@ function TimeleftCell(props) {
|
||||
const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true });
|
||||
|
||||
return (
|
||||
<TableRowCell className={styles.timeleft}>
|
||||
<Tooltip
|
||||
anchor={<Icon name={icons.INFO} />}
|
||||
tooltip={translate('DelayingDownloadUntil', { date, time })}
|
||||
kind={kinds.INVERSE}
|
||||
position={tooltipPositions.TOP}
|
||||
/>
|
||||
<TableRowCell
|
||||
className={styles.timeleft}
|
||||
title={translate('DelayingDownloadUntil', { date, time })}
|
||||
>
|
||||
-
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
@@ -44,13 +39,11 @@ function TimeleftCell(props) {
|
||||
const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true });
|
||||
|
||||
return (
|
||||
<TableRowCell className={styles.timeleft}>
|
||||
<Tooltip
|
||||
anchor={<Icon name={icons.INFO} />}
|
||||
tooltip={translate('RetryingDownloadOn', { date, time })}
|
||||
kind={kinds.INVERSE}
|
||||
position={tooltipPositions.TOP}
|
||||
/>
|
||||
<TableRowCell
|
||||
className={styles.timeleft}
|
||||
title={translate('RetryingDownloadOn', { date, time })}
|
||||
>
|
||||
-
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import TextInput from 'Components/Form/TextInput';
|
||||
import Icon from 'Components/Icon';
|
||||
import Button from 'Components/Link/Button';
|
||||
@@ -131,12 +130,7 @@ class AddNewMovie extends Component {
|
||||
<div className={styles.helpText}>
|
||||
{translate('FailedLoadingSearchResults')}
|
||||
</div>
|
||||
<Alert kind={kinds.WARNING}>{getErrorMessage(error)}</Alert>
|
||||
<div>
|
||||
<Link to="https://wiki.servarr.com/radarr/troubleshooting#invalid-response-received-from-tmdb">
|
||||
{translate('WhySearchesCouldBeFailing')}
|
||||
</Link>
|
||||
</div>
|
||||
<div>{getErrorMessage(error)}</div>
|
||||
</div> : null
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,10 @@ import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { clearAddMovie, lookupMovie } from 'Store/Actions/addMovieActions';
|
||||
import { clearMovieFiles, fetchMovieFiles } from 'Store/Actions/movieFileActions';
|
||||
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
|
||||
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||
import { fetchImportExclusions } from 'Store/Actions/Settings/importExclusions';
|
||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
|
||||
import selectUniqueIds from 'Utilities/Object/selectUniqueIds';
|
||||
import parseUrl from 'Utilities/String/parseUrl';
|
||||
import AddNewMovie from './AddNewMovie';
|
||||
|
||||
@@ -38,9 +35,7 @@ const mapDispatchToProps = {
|
||||
fetchRootFolders,
|
||||
fetchImportExclusions,
|
||||
fetchQueueDetails,
|
||||
clearQueueDetails,
|
||||
fetchMovieFiles,
|
||||
clearMovieFiles
|
||||
clearQueueDetails
|
||||
};
|
||||
|
||||
class AddNewMovieConnector extends Component {
|
||||
@@ -60,20 +55,6 @@ class AddNewMovieConnector extends Component {
|
||||
this.props.fetchQueueDetails();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
items
|
||||
} = this.props;
|
||||
|
||||
if (hasDifferentItems(prevProps.items, items)) {
|
||||
const movieIds = selectUniqueIds(items, 'internalId');
|
||||
|
||||
if (movieIds.length) {
|
||||
this.props.fetchMovieFiles({ movieId: movieIds });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this._movieLookupTimeout) {
|
||||
clearTimeout(this._movieLookupTimeout);
|
||||
@@ -81,7 +62,6 @@ class AddNewMovieConnector extends Component {
|
||||
|
||||
this.props.clearAddMovie();
|
||||
this.props.clearQueueDetails();
|
||||
this.props.clearMovieFiles();
|
||||
}
|
||||
|
||||
//
|
||||
@@ -127,15 +107,12 @@ class AddNewMovieConnector extends Component {
|
||||
|
||||
AddNewMovieConnector.propTypes = {
|
||||
term: PropTypes.string,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
lookupMovie: PropTypes.func.isRequired,
|
||||
clearAddMovie: PropTypes.func.isRequired,
|
||||
fetchRootFolders: PropTypes.func.isRequired,
|
||||
fetchImportExclusions: PropTypes.func.isRequired,
|
||||
fetchQueueDetails: PropTypes.func.isRequired,
|
||||
clearQueueDetails: PropTypes.func.isRequired,
|
||||
fetchMovieFiles: PropTypes.func.isRequired,
|
||||
clearMovieFiles: PropTypes.func.isRequired
|
||||
clearQueueDetails: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(AddNewMovieConnector);
|
||||
|
||||
@@ -20,10 +20,6 @@ class AddNewMovieModalContent extends Component {
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onQualityProfileIdChange = ({ value }) => {
|
||||
this.props.onInputChange({ name: 'qualityProfileId', value: parseInt(value) });
|
||||
};
|
||||
|
||||
onAddMoviePress = () => {
|
||||
this.props.onAddMoviePress();
|
||||
};
|
||||
@@ -40,7 +36,7 @@ class AddNewMovieModalContent extends Component {
|
||||
isAdding,
|
||||
rootFolderPath,
|
||||
monitor,
|
||||
qualityProfileId,
|
||||
qualityProfileIds,
|
||||
minimumAvailability,
|
||||
searchForMovie,
|
||||
folder,
|
||||
@@ -130,9 +126,9 @@ class AddNewMovieModalContent extends Component {
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.QUALITY_PROFILE_SELECT}
|
||||
name="qualityProfileId"
|
||||
onChange={this.onQualityProfileIdChange}
|
||||
{...qualityProfileId}
|
||||
name="qualityProfileIds"
|
||||
onChange={onInputChange}
|
||||
{...qualityProfileIds}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
@@ -189,7 +185,7 @@ AddNewMovieModalContent.propTypes = {
|
||||
addError: PropTypes.object,
|
||||
rootFolderPath: PropTypes.object,
|
||||
monitor: PropTypes.object.isRequired,
|
||||
qualityProfileId: PropTypes.object,
|
||||
qualityProfileIds: PropTypes.arrayOf(PropTypes.object),
|
||||
minimumAvailability: PropTypes.object.isRequired,
|
||||
searchForMovie: PropTypes.object.isRequired,
|
||||
folder: PropTypes.string.isRequired,
|
||||
|
||||
@@ -58,7 +58,7 @@ class AddNewMovieModalContentConnector extends Component {
|
||||
tmdbId,
|
||||
rootFolderPath,
|
||||
monitor,
|
||||
qualityProfileId,
|
||||
qualityProfileIds,
|
||||
minimumAvailability,
|
||||
searchForMovie,
|
||||
tags
|
||||
@@ -68,7 +68,7 @@ class AddNewMovieModalContentConnector extends Component {
|
||||
tmdbId,
|
||||
rootFolderPath: rootFolderPath.value,
|
||||
monitor: monitor.value,
|
||||
qualityProfileId: qualityProfileId.value,
|
||||
qualityProfileIds: qualityProfileIds.value,
|
||||
minimumAvailability: minimumAvailability.value,
|
||||
searchForMovie: searchForMovie.value,
|
||||
tags: tags.value
|
||||
@@ -93,7 +93,7 @@ AddNewMovieModalContentConnector.propTypes = {
|
||||
tmdbId: PropTypes.number.isRequired,
|
||||
rootFolderPath: PropTypes.object,
|
||||
monitor: PropTypes.object.isRequired,
|
||||
qualityProfileId: PropTypes.object,
|
||||
qualityProfileIds: PropTypes.arrayOf(PropTypes.object),
|
||||
minimumAvailability: PropTypes.object.isRequired,
|
||||
searchForMovie: PropTypes.object.isRequired,
|
||||
tags: PropTypes.object.isRequired,
|
||||
|
||||
@@ -85,13 +85,8 @@
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.studio,
|
||||
.genres {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.links {
|
||||
margin-left: 5px;
|
||||
margin-left: 8px;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ interface CssExports {
|
||||
'certification': string;
|
||||
'content': string;
|
||||
'exclusionIcon': string;
|
||||
'genres': string;
|
||||
'icons': string;
|
||||
'links': string;
|
||||
'overlay': string;
|
||||
@@ -15,7 +14,6 @@ interface CssExports {
|
||||
'runtime': string;
|
||||
'searchResult': string;
|
||||
'statusContainer': string;
|
||||
'studio': string;
|
||||
'title': string;
|
||||
'titleContainer': string;
|
||||
'titleRow': string;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
import ImdbRating from 'Components/ImdbRating';
|
||||
import Label from 'Components/Label';
|
||||
import Link from 'Components/Link/Link';
|
||||
import TmdbRating from 'Components/TmdbRating';
|
||||
@@ -62,13 +61,11 @@ class AddNewMovieSearchResult extends Component {
|
||||
titleSlug,
|
||||
year,
|
||||
studio,
|
||||
genres,
|
||||
status,
|
||||
overview,
|
||||
ratings,
|
||||
folder,
|
||||
images,
|
||||
existingMovieId,
|
||||
isExistingMovie,
|
||||
isExclusionMovie,
|
||||
isSmallScreen,
|
||||
@@ -76,19 +73,22 @@ class AddNewMovieSearchResult extends Component {
|
||||
id,
|
||||
monitored,
|
||||
isAvailable,
|
||||
movieFile,
|
||||
queueItem,
|
||||
queueStatus,
|
||||
queueState,
|
||||
runtime,
|
||||
movieRuntimeFormat,
|
||||
certification
|
||||
certification,
|
||||
statistics
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
movieFileCount
|
||||
} = statistics;
|
||||
|
||||
const {
|
||||
isNewAddMovieModalOpen
|
||||
} = this.state;
|
||||
|
||||
const hasMovieFile = !!movieFile;
|
||||
|
||||
const linkProps = isExistingMovie ? { to: `/movie/${titleSlug}` } : { onPress: this.onPress };
|
||||
const posterWidth = 167;
|
||||
const posterHeight = 250;
|
||||
@@ -124,13 +124,13 @@ class AddNewMovieSearchResult extends Component {
|
||||
{
|
||||
isExistingMovie &&
|
||||
<MovieIndexProgressBar
|
||||
movieId={existingMovieId}
|
||||
movieFile={movieFile}
|
||||
monitored={monitored}
|
||||
hasFile={hasMovieFile}
|
||||
hasFile={movieFileCount > 0}
|
||||
status={status}
|
||||
width={posterWidth}
|
||||
detailedProgressBar={true}
|
||||
queueStatus={queueStatus}
|
||||
queueState={queueState}
|
||||
isAvailable={isAvailable}
|
||||
/>
|
||||
}
|
||||
@@ -201,46 +201,13 @@ class AddNewMovieSearchResult extends Component {
|
||||
/>
|
||||
</Label>
|
||||
|
||||
{
|
||||
ratings.imdb ?
|
||||
<Label size={sizes.LARGE}>
|
||||
<ImdbRating
|
||||
ratings={ratings}
|
||||
iconSize={13}
|
||||
/>
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
!!studio &&
|
||||
<Label size={sizes.LARGE}>
|
||||
<Icon
|
||||
name={icons.STUDIO}
|
||||
size={13}
|
||||
/>
|
||||
|
||||
<span className={styles.studio}>
|
||||
{studio}
|
||||
</span>
|
||||
{studio}
|
||||
</Label>
|
||||
}
|
||||
|
||||
{
|
||||
genres.length > 0 ?
|
||||
<Label size={sizes.LARGE}>
|
||||
<Icon
|
||||
name={icons.GENRE}
|
||||
size={13}
|
||||
/>
|
||||
|
||||
<span className={styles.genres}>
|
||||
{genres.slice(0, 3).join(', ')}
|
||||
</span>
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
<Tooltip
|
||||
anchor={
|
||||
<Label
|
||||
@@ -252,15 +219,15 @@ class AddNewMovieSearchResult extends Component {
|
||||
/>
|
||||
|
||||
<span className={styles.links}>
|
||||
{translate('Links')}
|
||||
Links
|
||||
</span>
|
||||
</Label>
|
||||
}
|
||||
tooltip={
|
||||
<MovieDetailsLinks
|
||||
tmdbId={tmdbId}
|
||||
imdbId={imdbId}
|
||||
youTubeTrailerId={youTubeTrailerId}
|
||||
imdbId={imdbId}
|
||||
/>
|
||||
}
|
||||
canFlip={true}
|
||||
@@ -271,10 +238,9 @@ class AddNewMovieSearchResult extends Component {
|
||||
{
|
||||
isExistingMovie && isSmallScreen &&
|
||||
<MovieStatusLabel
|
||||
hasMovieFiles={hasMovieFile}
|
||||
hasMovieFiles={movieFileCount > 0}
|
||||
monitored={monitored}
|
||||
isAvailable={isAvailable}
|
||||
queueItem={queueItem}
|
||||
id={id}
|
||||
useLabel={true}
|
||||
colorImpairedMode={colorImpairedMode}
|
||||
@@ -311,29 +277,32 @@ AddNewMovieSearchResult.propTypes = {
|
||||
titleSlug: PropTypes.string.isRequired,
|
||||
year: PropTypes.number.isRequired,
|
||||
studio: PropTypes.string,
|
||||
genres: PropTypes.arrayOf(PropTypes.string),
|
||||
status: PropTypes.string.isRequired,
|
||||
overview: PropTypes.string,
|
||||
ratings: PropTypes.object.isRequired,
|
||||
folder: PropTypes.string.isRequired,
|
||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
existingMovieId: PropTypes.number,
|
||||
isExistingMovie: PropTypes.bool.isRequired,
|
||||
isExclusionMovie: PropTypes.bool.isRequired,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
id: PropTypes.number,
|
||||
queueItems: PropTypes.arrayOf(PropTypes.object),
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
hasFile: PropTypes.bool.isRequired,
|
||||
isAvailable: PropTypes.bool.isRequired,
|
||||
movieFile: PropTypes.object,
|
||||
queueItem: PropTypes.object,
|
||||
colorImpairedMode: PropTypes.bool,
|
||||
queueStatus: PropTypes.string,
|
||||
queueState: PropTypes.string,
|
||||
runtime: PropTypes.number.isRequired,
|
||||
movieRuntimeFormat: PropTypes.string.isRequired,
|
||||
certification: PropTypes.string
|
||||
certification: PropTypes.string,
|
||||
statistics: PropTypes.object
|
||||
};
|
||||
|
||||
AddNewMovieSearchResult.defaultProps = {
|
||||
genres: []
|
||||
statistics: {
|
||||
movieFileCount: 0
|
||||
}
|
||||
};
|
||||
|
||||
export default AddNewMovieSearchResult;
|
||||
|
||||
@@ -11,21 +11,16 @@ function createMapStateToProps() {
|
||||
createExclusionMovieSelector(),
|
||||
createDimensionsSelector(),
|
||||
(state) => state.queue.details.items,
|
||||
(state) => state.movieFiles.items,
|
||||
(state, { internalId }) => internalId,
|
||||
(state) => state.settings.ui.item.movieRuntimeFormat,
|
||||
(isExistingMovie, isExclusionMovie, dimensions, queueItems, movieFiles, internalId, movieRuntimeFormat) => {
|
||||
const queueItem = queueItems.find((item) => internalId > 0 && item.movieId === internalId);
|
||||
const movieFile = movieFiles.find((item) => internalId > 0 && item.movieId === internalId);
|
||||
(isExistingMovie, isExclusionMovie, dimensions, queueItems, internalId) => {
|
||||
const firstQueueItem = queueItems.find((q) => q.movieId === internalId && internalId > 0);
|
||||
|
||||
return {
|
||||
existingMovieId: internalId,
|
||||
isExistingMovie,
|
||||
isExclusionMovie,
|
||||
isSmallScreen: dimensions.isSmallScreen,
|
||||
queueItem,
|
||||
movieFile,
|
||||
movieRuntimeFormat
|
||||
queueStatus: firstQueueItem ? firstQueueItem.status : null,
|
||||
queueState: firstQueueItem ? firstQueueItem.trackedDownloadState : null
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -25,13 +25,13 @@ class ImportMovieFooter extends Component {
|
||||
|
||||
const {
|
||||
defaultMonitor,
|
||||
defaultQualityProfileId,
|
||||
defaultQualityProfileIds,
|
||||
defaultMinimumAvailability
|
||||
} = props;
|
||||
|
||||
this.state = {
|
||||
monitor: defaultMonitor,
|
||||
qualityProfileId: defaultQualityProfileId,
|
||||
qualityProfileIds: defaultQualityProfileIds,
|
||||
minimumAvailability: defaultMinimumAvailability
|
||||
};
|
||||
}
|
||||
@@ -39,16 +39,16 @@ class ImportMovieFooter extends Component {
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const {
|
||||
defaultMonitor,
|
||||
defaultQualityProfileId,
|
||||
defaultQualityProfileIds,
|
||||
defaultMinimumAvailability,
|
||||
isMonitorMixed,
|
||||
isQualityProfileIdMixed,
|
||||
isQualityProfileIdsMixed,
|
||||
isMinimumAvailabilityMixed
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
monitor,
|
||||
qualityProfileId,
|
||||
qualityProfileIds,
|
||||
minimumAvailability
|
||||
} = this.state;
|
||||
|
||||
@@ -60,10 +60,10 @@ class ImportMovieFooter extends Component {
|
||||
newState.monitor = defaultMonitor;
|
||||
}
|
||||
|
||||
if (isQualityProfileIdMixed && qualityProfileId !== MIXED) {
|
||||
newState.qualityProfileId = MIXED;
|
||||
} else if (!isQualityProfileIdMixed && qualityProfileId !== defaultQualityProfileId) {
|
||||
newState.qualityProfileId = defaultQualityProfileId;
|
||||
if (isQualityProfileIdsMixed && qualityProfileIds !== MIXED) {
|
||||
newState.qualityProfileIds = MIXED;
|
||||
} else if (!isQualityProfileIdsMixed && qualityProfileIds !== defaultQualityProfileIds) {
|
||||
newState.qualityProfileIds = defaultQualityProfileIds;
|
||||
}
|
||||
|
||||
if (isMinimumAvailabilityMixed && minimumAvailability !== MIXED) {
|
||||
@@ -94,7 +94,7 @@ class ImportMovieFooter extends Component {
|
||||
isImporting,
|
||||
isLookingUpMovie,
|
||||
isMonitorMixed,
|
||||
isQualityProfileIdMixed,
|
||||
isQualityProfileIdsMixed,
|
||||
isMinimumAvailabilityMixed,
|
||||
hasUnsearchedItems,
|
||||
importError,
|
||||
@@ -105,7 +105,7 @@ class ImportMovieFooter extends Component {
|
||||
|
||||
const {
|
||||
monitor,
|
||||
qualityProfileId,
|
||||
qualityProfileIds,
|
||||
minimumAvailability
|
||||
} = this.state;
|
||||
|
||||
@@ -148,10 +148,10 @@ class ImportMovieFooter extends Component {
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.QUALITY_PROFILE_SELECT}
|
||||
name="qualityProfileId"
|
||||
value={qualityProfileId}
|
||||
name="qualityProfileIds"
|
||||
value={qualityProfileIds}
|
||||
isDisabled={!selectedCount}
|
||||
includeMixed={isQualityProfileIdMixed}
|
||||
includeMixed={isQualityProfileIdsMixed}
|
||||
onChange={this.onInputChange}
|
||||
/>
|
||||
</div>
|
||||
@@ -257,10 +257,10 @@ ImportMovieFooter.propTypes = {
|
||||
isImporting: PropTypes.bool.isRequired,
|
||||
isLookingUpMovie: PropTypes.bool.isRequired,
|
||||
defaultMonitor: PropTypes.string.isRequired,
|
||||
defaultQualityProfileId: PropTypes.number,
|
||||
defaultQualityProfileIds: PropTypes.arrayOf(PropTypes.number),
|
||||
defaultMinimumAvailability: PropTypes.string,
|
||||
isMonitorMixed: PropTypes.bool.isRequired,
|
||||
isQualityProfileIdMixed: PropTypes.bool.isRequired,
|
||||
isQualityProfileIdsMixed: PropTypes.bool.isRequired,
|
||||
isMinimumAvailabilityMixed: PropTypes.bool.isRequired,
|
||||
hasUnsearchedItems: PropTypes.bool.isRequired,
|
||||
importError: PropTypes.object,
|
||||
|
||||
@@ -18,7 +18,7 @@ function createMapStateToProps() {
|
||||
(addMovie, importMovie, selectedIds) => {
|
||||
const {
|
||||
monitor: defaultMonitor,
|
||||
qualityProfileId: defaultQualityProfileId,
|
||||
qualityProfileIds: defaultQualityProfileIds,
|
||||
minimumAvailability: defaultMinimumAvailability
|
||||
} = addMovie.defaults;
|
||||
|
||||
@@ -30,7 +30,7 @@ function createMapStateToProps() {
|
||||
} = importMovie;
|
||||
|
||||
const isMonitorMixed = isMixed(items, selectedIds, defaultMonitor, 'monitor');
|
||||
const isQualityProfileIdMixed = isMixed(items, selectedIds, defaultQualityProfileId, 'qualityProfileId');
|
||||
const isQualityProfileIdsMixed = isMixed(items, selectedIds, defaultQualityProfileIds, 'qualityProfileIds');
|
||||
const isMinimumAvailabilityMixed = isMixed(items, selectedIds, defaultMinimumAvailability, 'minimumAvailability');
|
||||
const hasUnsearchedItems = !isLookingUpMovie && items.some((item) => !item.isPopulated);
|
||||
|
||||
@@ -39,10 +39,10 @@ function createMapStateToProps() {
|
||||
isLookingUpMovie,
|
||||
isImporting,
|
||||
defaultMonitor,
|
||||
defaultQualityProfileId,
|
||||
defaultQualityProfileIds,
|
||||
defaultMinimumAvailability,
|
||||
isMonitorMixed,
|
||||
isQualityProfileIdMixed,
|
||||
isQualityProfileIdsMixed,
|
||||
isMinimumAvailabilityMixed,
|
||||
importError,
|
||||
hasUnsearchedItems
|
||||
|
||||
@@ -10,9 +10,8 @@ import styles from './ImportMovieRow.css';
|
||||
function ImportMovieRow(props) {
|
||||
const {
|
||||
id,
|
||||
relativePath,
|
||||
monitor,
|
||||
qualityProfileId,
|
||||
qualityProfileIds,
|
||||
minimumAvailability,
|
||||
selectedMovie,
|
||||
isExistingMovie,
|
||||
@@ -32,7 +31,7 @@ function ImportMovieRow(props) {
|
||||
/>
|
||||
|
||||
<VirtualTableRowCell className={styles.folder}>
|
||||
{relativePath}
|
||||
{id}
|
||||
</VirtualTableRowCell>
|
||||
|
||||
<VirtualTableRowCell className={styles.movie}>
|
||||
@@ -63,8 +62,8 @@ function ImportMovieRow(props) {
|
||||
<VirtualTableRowCell className={styles.qualityProfile}>
|
||||
<FormInputGroup
|
||||
type={inputTypes.QUALITY_PROFILE_SELECT}
|
||||
name="qualityProfileId"
|
||||
value={qualityProfileId}
|
||||
name="qualityProfileIds"
|
||||
value={qualityProfileIds}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</VirtualTableRowCell>
|
||||
@@ -74,9 +73,8 @@ function ImportMovieRow(props) {
|
||||
|
||||
ImportMovieRow.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
relativePath: PropTypes.string.isRequired,
|
||||
monitor: PropTypes.string.isRequired,
|
||||
qualityProfileId: PropTypes.number.isRequired,
|
||||
qualityProfileIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
minimumAvailability: PropTypes.string.isRequired,
|
||||
selectedMovie: PropTypes.object,
|
||||
isExistingMovie: PropTypes.bool.isRequired,
|
||||
|
||||
@@ -15,7 +15,7 @@ class ImportMovieTable extends Component {
|
||||
const {
|
||||
unmappedFolders,
|
||||
defaultMonitor,
|
||||
defaultQualityProfileId,
|
||||
defaultQualityProfileIds,
|
||||
defaultMinimumAvailability,
|
||||
onMovieLookup,
|
||||
onSetImportMovieValue
|
||||
@@ -23,14 +23,14 @@ class ImportMovieTable extends Component {
|
||||
|
||||
const values = {
|
||||
monitor: defaultMonitor,
|
||||
qualityProfileId: defaultQualityProfileId,
|
||||
qualityProfileIds: defaultQualityProfileIds,
|
||||
minimumAvailability: defaultMinimumAvailability
|
||||
};
|
||||
|
||||
unmappedFolders.forEach((unmappedFolder) => {
|
||||
const id = unmappedFolder.name;
|
||||
|
||||
onMovieLookup(id, unmappedFolder.path, unmappedFolder.relativePath);
|
||||
onMovieLookup(id, unmappedFolder.path);
|
||||
|
||||
onSetImportMovieValue({
|
||||
id,
|
||||
@@ -167,7 +167,7 @@ ImportMovieTable.propTypes = {
|
||||
items: PropTypes.arrayOf(PropTypes.object),
|
||||
unmappedFolders: PropTypes.arrayOf(PropTypes.object),
|
||||
defaultMonitor: PropTypes.string.isRequired,
|
||||
defaultQualityProfileId: PropTypes.number,
|
||||
defaultQualityProfileIds: PropTypes.arrayOf(PropTypes.number),
|
||||
defaultMinimumAvailability: PropTypes.string,
|
||||
allSelected: PropTypes.bool.isRequired,
|
||||
allUnselected: PropTypes.bool.isRequired,
|
||||
|
||||
@@ -13,7 +13,7 @@ function createMapStateToProps() {
|
||||
(addMovie, importMovie, dimensions, allMovies) => {
|
||||
return {
|
||||
defaultMonitor: addMovie.defaults.monitor,
|
||||
defaultQualityProfileId: addMovie.defaults.qualityProfileId,
|
||||
defaultQualityProfileIds: addMovie.defaults.qualityProfileIds,
|
||||
defaultMinimumAvailability: addMovie.defaults.minimumAvailability,
|
||||
items: importMovie.items,
|
||||
isSmallScreen: dimensions.isSmallScreen,
|
||||
@@ -25,11 +25,10 @@ function createMapStateToProps() {
|
||||
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
onMovieLookup(name, path, relativePath) {
|
||||
onMovieLookup(name, path) {
|
||||
dispatch(queueLookupMovie({
|
||||
name,
|
||||
path,
|
||||
relativePath,
|
||||
term: name
|
||||
}));
|
||||
},
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
.contentContainer {
|
||||
z-index: $popperZIndex;
|
||||
margin-top: 4px;
|
||||
/* 400px container width with 8px padding on each side */
|
||||
/* 400px container witdh with 8px padding on each side */
|
||||
width: 384px;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import FormInputButton from 'Components/Form/FormInputButton';
|
||||
import TextInput from 'Components/Form/TextInput';
|
||||
import Icon from 'Components/Icon';
|
||||
import Link from 'Components/Link/Link';
|
||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import Portal from 'Components/Portal';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
@@ -242,7 +243,7 @@ class ImportMovieSelectMovie extends Component {
|
||||
<FormInputButton
|
||||
kind={kinds.DEFAULT}
|
||||
spinnerIcon={icons.REFRESH}
|
||||
canSpin={true}
|
||||
ButtonComponent={SpinnerButton}
|
||||
isSpinning={isFetching}
|
||||
onPress={this.onRefreshPress}
|
||||
>
|
||||
|
||||
@@ -148,7 +148,7 @@ class ImportMovieSelectFolder extends Component {
|
||||
className={styles.addErrorAlert}
|
||||
kind={kinds.DANGER}
|
||||
>
|
||||
{translate('AddRootFolderError')}
|
||||
{translate('UnableToAddRootFolder')}
|
||||
|
||||
<ul>
|
||||
{
|
||||
|
||||
@@ -5,13 +5,12 @@ import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { addRootFolder, deleteRootFolder, fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||
import createRootFoldersSelector from 'Store/Selectors/createRootFoldersSelector';
|
||||
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
|
||||
import ImportMovieSelectFolder from './ImportMovieSelectFolder';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createRootFoldersSelector(),
|
||||
(state) => state.rootFolders,
|
||||
createSystemStatusSelector(),
|
||||
(rootFolders, systemStatus) => {
|
||||
return {
|
||||
|
||||
@@ -65,12 +65,12 @@ function AppUpdatedModalContent(props) {
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{translate('AppUpdated')}
|
||||
{translate('AppUpdated', { appName: 'Radarr' })}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<div>
|
||||
<InlineMarkdown data={translate('AppUpdatedVersion', { version })} blockClassName={styles.version} />
|
||||
<InlineMarkdown data={translate('AppUpdatedVersion', { appName: 'Radarr', version })} blockClassName={styles.version} />
|
||||
</div>
|
||||
|
||||
{
|
||||
|
||||
@@ -28,11 +28,11 @@ function ConnectionLostModal(props) {
|
||||
|
||||
<ModalBody>
|
||||
<div>
|
||||
{translate('ConnectionLostToBackend')}
|
||||
{translate('ConnectionLostToBackend', { appName: 'Radarr' })}
|
||||
</div>
|
||||
|
||||
<div className={styles.automatic}>
|
||||
{translate('ConnectionLostReconnect')}
|
||||
{translate('ConnectionLostReconnect', { appName: 'Radarr' })}
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import SortDirection from 'Helpers/Props/SortDirection';
|
||||
import { FilterBuilderProp } from './AppState';
|
||||
|
||||
export interface Error {
|
||||
responseJSON: {
|
||||
@@ -21,10 +20,6 @@ export interface PagedAppSectionState {
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
export interface AppSectionFilterState<T> {
|
||||
filterBuilderProps: FilterBuilderProp<T>[];
|
||||
}
|
||||
|
||||
export interface AppSectionSchemaState<T> {
|
||||
isSchemaFetching: boolean;
|
||||
isSchemaPopulated: boolean;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import InteractiveImportAppState from 'App/State/InteractiveImportAppState';
|
||||
import CalendarAppState from './CalendarAppState';
|
||||
import CommandAppState from './CommandAppState';
|
||||
import HistoryAppState from './HistoryAppState';
|
||||
import MovieCollectionAppState from './MovieCollectionAppState';
|
||||
import MovieFilesAppState from './MovieFilesAppState';
|
||||
import MoviesAppState, { MovieIndexAppState } from './MoviesAppState';
|
||||
@@ -44,19 +43,9 @@ export interface CustomFilter {
|
||||
filers: PropertyFilter[];
|
||||
}
|
||||
|
||||
export interface AppSectionState {
|
||||
dimensions: {
|
||||
isSmallScreen: boolean;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface AppState {
|
||||
app: AppSectionState;
|
||||
calendar: CalendarAppState;
|
||||
commands: CommandAppState;
|
||||
history: HistoryAppState;
|
||||
interactiveImport: InteractiveImportAppState;
|
||||
movieCollections: MovieCollectionAppState;
|
||||
movieFiles: MovieFilesAppState;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import AppSectionState, {
|
||||
AppSectionFilterState,
|
||||
} from 'App/State/AppSectionState';
|
||||
import AppSectionState from 'App/State/AppSectionState';
|
||||
import Movie from 'Movie/Movie';
|
||||
import { FilterBuilderProp } from './AppState';
|
||||
|
||||
interface CalendarAppState
|
||||
extends AppSectionState<Movie>,
|
||||
AppSectionFilterState<Movie> {}
|
||||
interface CalendarAppState extends AppSectionState<Movie> {
|
||||
filterBuilderProps: FilterBuilderProp<Movie>[];
|
||||
}
|
||||
|
||||
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 MovieCollection from 'typings/MovieCollection';
|
||||
|
||||
interface MovieCollectionAppState extends AppSectionState<MovieCollection> {
|
||||
itemMap: Record<number, number>;
|
||||
}
|
||||
type MovieCollectionAppState = AppSectionState<MovieCollection>;
|
||||
|
||||
export default MovieCollectionAppState;
|
||||
|
||||
@@ -1,17 +1,41 @@
|
||||
import Queue from 'typings/Queue';
|
||||
import AppSectionState, {
|
||||
AppSectionFilterState,
|
||||
AppSectionItemState,
|
||||
Error,
|
||||
} from './AppSectionState';
|
||||
import ModelBase from 'App/ModelBase';
|
||||
import Language from 'Language/Language';
|
||||
import { QualityModel } from 'Quality/Quality';
|
||||
import CustomFormat from 'typings/CustomFormat';
|
||||
import AppSectionState, { AppSectionItemState, Error } from './AppSectionState';
|
||||
|
||||
export interface StatusMessage {
|
||||
title: string;
|
||||
messages: string[];
|
||||
}
|
||||
|
||||
export interface Queue extends ModelBase {
|
||||
languages: Language[];
|
||||
quality: QualityModel;
|
||||
customFormats: CustomFormat[];
|
||||
size: number;
|
||||
title: string;
|
||||
sizeleft: number;
|
||||
timeleft: string;
|
||||
estimatedCompletionTime: string;
|
||||
status: string;
|
||||
trackedDownloadStatus: string;
|
||||
trackedDownloadState: string;
|
||||
statusMessages: StatusMessage[];
|
||||
errorMessage: string;
|
||||
downloadId: string;
|
||||
protocol: string;
|
||||
downloadClient: string;
|
||||
outputPath: string;
|
||||
movieHasFile: boolean;
|
||||
movieId?: number;
|
||||
}
|
||||
|
||||
export interface QueueDetailsAppState extends AppSectionState<Queue> {
|
||||
params: unknown;
|
||||
}
|
||||
|
||||
export interface QueuePagedAppState
|
||||
extends AppSectionState<Queue>,
|
||||
AppSectionFilterState<Queue> {
|
||||
export interface QueuePagedAppState extends AppSectionState<Queue> {
|
||||
isGrabbing: boolean;
|
||||
grabError: Error;
|
||||
isRemoving: boolean;
|
||||
|
||||
@@ -42,9 +42,9 @@ function Agenda(props) {
|
||||
<div className={styles.agenda}>
|
||||
{
|
||||
items.map((item, index) => {
|
||||
const momentDate = moment(item.sortDate);
|
||||
const momentDate = moment(item.inCinemas);
|
||||
const showDate = index === 0 ||
|
||||
!moment(items[index - 1].sortDate).isSame(momentDate, 'day');
|
||||
!moment(items[index - 1].inCinemas).isSame(momentDate, 'day');
|
||||
|
||||
return (
|
||||
<AgendaEventConnector
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointSmall) {
|
||||
.overlay {
|
||||
.event {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@@ -111,4 +111,5 @@
|
||||
.releaseIcon {
|
||||
margin-right: 20px;
|
||||
width: 25px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ class AgendaEvent extends Component {
|
||||
|
||||
<div className={styles.overlay}>
|
||||
<div className={styles.date}>
|
||||
{showDate ? startTime.format(longDateFormat) : null}
|
||||
{(showDate) ? startTime.format(longDateFormat) : null}
|
||||
</div>
|
||||
|
||||
<div className={styles.releaseIcon}>
|
||||
@@ -147,7 +147,7 @@ class AgendaEvent extends Component {
|
||||
className={styles.statusIcon}
|
||||
name={icons.MOVIE_FILE}
|
||||
kind={kinds.WARNING}
|
||||
title={translate('QualityCutoffNotMet')}
|
||||
title={translate('QualityCutoffHasNotBeenMet')}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -33,7 +33,9 @@ class Calendar extends Component {
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<Alert kind={kinds.DANGER}>{translate('CalendarLoadError')}</Alert>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadTheCalendar')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -55,7 +55,7 @@ class CalendarConnector extends Component {
|
||||
gotoCalendarToday
|
||||
} = this.props;
|
||||
|
||||
registerPagePopulator(this.repopulate, ['movieFileUpdated', 'movieFileDeleted']);
|
||||
registerPagePopulator(this.repopulate);
|
||||
|
||||
if (useCurrentPage) {
|
||||
fetchCalendar();
|
||||
|
||||
@@ -23,11 +23,13 @@ function createFilterBuilderPropsSelector() {
|
||||
);
|
||||
}
|
||||
|
||||
interface CalendarFilterModalProps {
|
||||
interface SeriesIndexFilterModalProps {
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
export default function CalendarFilterModal(props: CalendarFilterModalProps) {
|
||||
export default function CalendarFilterModal(
|
||||
props: SeriesIndexFilterModalProps
|
||||
) {
|
||||
const sectionItems = useSelector(createCalendarSelector());
|
||||
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
|
||||
const customFilterType = 'calendar';
|
||||
|
||||
@@ -104,7 +104,7 @@ class CalendarPage extends Component {
|
||||
<PageToolbar>
|
||||
<PageToolbarSection>
|
||||
<PageToolbarButton
|
||||
label={translate('ICalLink')}
|
||||
label={translate('iCalLink')}
|
||||
iconName={icons.CALENDAR}
|
||||
onPress={this.onGetCalendarLinkPress}
|
||||
/>
|
||||
@@ -112,7 +112,7 @@ class CalendarPage extends Component {
|
||||
<PageToolbarSeparator />
|
||||
|
||||
<PageToolbarButton
|
||||
label={translate('RssSync')}
|
||||
label={translate('RSSSync')}
|
||||
iconName={icons.RSS}
|
||||
isSpinning={isRssSyncExecuting}
|
||||
onPress={onRssSyncPress}
|
||||
@@ -180,7 +180,7 @@ class CalendarPage extends Component {
|
||||
|
||||
{
|
||||
!movieError && movieIsPopulated && !hasMovie &&
|
||||
<NoMovie totalItems={0} />
|
||||
<NoMovie />
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -25,7 +25,7 @@ function createMissingMovieIdsSelector() {
|
||||
const inCinemas = movie.inCinemas;
|
||||
|
||||
if (
|
||||
!movie.hasFile &&
|
||||
(!movie.statistics || movie.statistics.movieFileCount === 0) &&
|
||||
moment(inCinemas).isAfter(start) &&
|
||||
moment(inCinemas).isBefore(end) &&
|
||||
isBefore(movie.inCinemas) &&
|
||||
|
||||
@@ -48,10 +48,6 @@ $fullColorGradient: rgba(244, 245, 246, 0.2);
|
||||
.statusContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:global(.fullColor) {
|
||||
filter: var(--calendarFullColorFilter)
|
||||
}
|
||||
}
|
||||
|
||||
.statusIcon {
|
||||
|
||||
@@ -76,18 +76,12 @@ class CalendarEvent extends Component {
|
||||
{title}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={classNames(
|
||||
styles.statusContainer,
|
||||
fullColorEvents && 'fullColor'
|
||||
)}
|
||||
>
|
||||
<div className={styles.statusContainer}>
|
||||
{
|
||||
queueItem ?
|
||||
<span className={styles.statusIcon}>
|
||||
<CalendarEventQueueDetails
|
||||
{...queueItem}
|
||||
fullColorEvents={fullColorEvents}
|
||||
/>
|
||||
</span> :
|
||||
null
|
||||
@@ -104,14 +98,12 @@ class CalendarEvent extends Component {
|
||||
}
|
||||
|
||||
{
|
||||
showCutoffUnmetIcon &&
|
||||
!!movieFile &&
|
||||
movieFile.qualityCutoffNotMet ?
|
||||
showCutoffUnmetIcon && !!movieFile && movieFile.qualityCutoffNotMet ?
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={icons.MOVIE_FILE}
|
||||
kind={kinds.WARNING}
|
||||
title={translate('QualityCutoffNotMet')}
|
||||
title={translate('QualityCutoffHasNotBeenMet')}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ class CalendarHeader extends Component {
|
||||
isDisabled={view === calendarViews.AGENDA}
|
||||
onPress={onTodayPress}
|
||||
>
|
||||
{translate('Today')}
|
||||
Today
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -20,11 +20,10 @@ function Legend(props) {
|
||||
if (showCutoffUnmetIcon) {
|
||||
iconsToShow.push(
|
||||
<LegendIconItem
|
||||
name={translate('CutoffNotMet')}
|
||||
name={translate('CutoffUnmet')}
|
||||
icon={icons.MOVIE_FILE}
|
||||
kind={kinds.WARNING}
|
||||
fullColorEvents={fullColorEvents}
|
||||
tooltip={translate('QualityCutoffNotMet')}
|
||||
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
|
||||
tooltip={translate('QualityOrLangCutoffHasNotBeenMet')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,8 +4,4 @@
|
||||
|
||||
.icon {
|
||||
margin-right: 5px;
|
||||
|
||||
&:global(.fullColorEvents) {
|
||||
filter: var(--calendarFullColorFilter)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
@@ -7,9 +6,9 @@ import styles from './LegendIconItem.css';
|
||||
function LegendIconItem(props) {
|
||||
const {
|
||||
name,
|
||||
fullColorEvents,
|
||||
icon,
|
||||
kind,
|
||||
darken,
|
||||
tooltip
|
||||
} = props;
|
||||
|
||||
@@ -19,11 +18,9 @@ function LegendIconItem(props) {
|
||||
title={tooltip}
|
||||
>
|
||||
<Icon
|
||||
className={classNames(
|
||||
styles.icon,
|
||||
fullColorEvents && 'fullColorEvents'
|
||||
)}
|
||||
className={styles.icon}
|
||||
name={icon}
|
||||
darken={darken}
|
||||
kind={kind}
|
||||
/>
|
||||
|
||||
@@ -34,10 +31,14 @@ function LegendIconItem(props) {
|
||||
|
||||
LegendIconItem.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
fullColorEvents: PropTypes.bool.isRequired,
|
||||
icon: PropTypes.object.isRequired,
|
||||
kind: PropTypes.string.isRequired,
|
||||
darken: PropTypes.bool.isRequired,
|
||||
tooltip: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
LegendIconItem.defaultProps = {
|
||||
darken: false
|
||||
};
|
||||
|
||||
export default LegendIconItem;
|
||||
|
||||
@@ -135,7 +135,7 @@ class CalendarOptionsModalContent extends Component {
|
||||
type={inputTypes.CHECK}
|
||||
name="showCutoffUnmetIcon"
|
||||
value={showCutoffUnmetIcon}
|
||||
helpText={translate('IconForCutoffUnmetHelpText')}
|
||||
helpText={translate('ShowCutoffUnmetIconHelpText')}
|
||||
onChange={this.onOptionInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
@@ -177,7 +177,7 @@ class CalendarOptionsModalContent extends Component {
|
||||
values={weekColumnOptions}
|
||||
value={calendarWeekColumnHeader}
|
||||
onChange={this.onGlobalInputChange}
|
||||
helpText={translate('WeekColumnHeaderHelpText')}
|
||||
helpText={translate('SettingsWeekColumnHeaderHelpText')}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ class CalendarLinkModalContent extends Component {
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{translate('CalendarFeed')}
|
||||
{translate('RadarrCalendarFeed')}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
@@ -121,19 +121,19 @@ class CalendarLinkModalContent extends Component {
|
||||
type={inputTypes.CHECK}
|
||||
name="unmonitored"
|
||||
value={unmonitored}
|
||||
helpText={translate('ICalIncludeUnmonitoredMoviesHelpText')}
|
||||
helpText={translate('UnmonitoredHelpText')}
|
||||
onChange={this.onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('ICalShowAsAllDayEvents')}</FormLabel>
|
||||
<FormLabel>{translate('ShowAsAllDayEvents')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="asAllDay"
|
||||
value={asAllDay}
|
||||
helpText={translate('ICalShowAsAllDayEventsHelpText')}
|
||||
helpText={translate('AsAllDayHelpText')}
|
||||
onChange={this.onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
@@ -145,7 +145,7 @@ class CalendarLinkModalContent extends Component {
|
||||
type={inputTypes.TAG}
|
||||
name="tags"
|
||||
value={tags}
|
||||
helpText={translate('ICalTagsMoviesHelpText')}
|
||||
helpText={translate('TagsHelpText')}
|
||||
onChange={this.onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
@@ -160,7 +160,7 @@ class CalendarLinkModalContent extends Component {
|
||||
name="iCalHttpUrl"
|
||||
value={iCalHttpUrl}
|
||||
readOnly={true}
|
||||
helpText={translate('ICalFeedHelpText')}
|
||||
helpText={translate('ICalHttpUrlHelpText')}
|
||||
buttons={[
|
||||
<ClipboardButton
|
||||
key="copy"
|
||||
|
||||
@@ -46,7 +46,7 @@ class AddNewCollectionMovieModalContent extends Component {
|
||||
onInputChange,
|
||||
rootFolderPath,
|
||||
monitor,
|
||||
qualityProfileId,
|
||||
qualityProfileIds,
|
||||
minimumAvailability,
|
||||
searchForMovie
|
||||
} = this.props;
|
||||
@@ -126,13 +126,13 @@ class AddNewCollectionMovieModalContent extends Component {
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('QualityProfile')}</FormLabel>
|
||||
<FormLabel>{translate('QualityProfiles')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.QUALITY_PROFILE_SELECT}
|
||||
name="qualityProfileId"
|
||||
name="qualityProfileIds"
|
||||
onChange={this.onQualityProfileIdChange}
|
||||
{...qualityProfileId}
|
||||
{...qualityProfileIds}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
@@ -189,7 +189,7 @@ AddNewCollectionMovieModalContent.propTypes = {
|
||||
addError: PropTypes.object,
|
||||
rootFolderPath: PropTypes.object,
|
||||
monitor: PropTypes.object.isRequired,
|
||||
qualityProfileId: PropTypes.object,
|
||||
qualityProfileIds: PropTypes.object,
|
||||
minimumAvailability: PropTypes.object.isRequired,
|
||||
searchForMovie: PropTypes.object.isRequired,
|
||||
folder: PropTypes.string.isRequired,
|
||||
|
||||
@@ -25,7 +25,7 @@ function createMapStateToProps() {
|
||||
const collectionDefaults = {
|
||||
rootFolderPath: collection.rootFolderPath,
|
||||
monitor: 'movieOnly',
|
||||
qualityProfileId: collection.qualityProfileId,
|
||||
qualityProfileIds: collection.qualityProfileIds,
|
||||
minimumAvailability: collection.minimumAvailability,
|
||||
searchForMovie: collection.searchOnAdd,
|
||||
tags: collection.tags || []
|
||||
@@ -70,7 +70,7 @@ class AddNewCollectionMovieModalContentConnector extends Component {
|
||||
title,
|
||||
rootFolderPath,
|
||||
monitor,
|
||||
qualityProfileId,
|
||||
qualityProfileIds,
|
||||
minimumAvailability,
|
||||
searchForMovie,
|
||||
tags
|
||||
@@ -81,7 +81,7 @@ class AddNewCollectionMovieModalContentConnector extends Component {
|
||||
title,
|
||||
rootFolderPath: rootFolderPath.value,
|
||||
monitor: monitor.value,
|
||||
qualityProfileId: qualityProfileId.value,
|
||||
qualityProfileIds: qualityProfileIds.value,
|
||||
minimumAvailability: minimumAvailability.value,
|
||||
searchForMovie: searchForMovie.value,
|
||||
tags: tags.value
|
||||
@@ -109,7 +109,7 @@ AddNewCollectionMovieModalContentConnector.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
rootFolderPath: PropTypes.object,
|
||||
monitor: PropTypes.object.isRequired,
|
||||
qualityProfileId: PropTypes.object,
|
||||
qualityProfileIds: PropTypes.object,
|
||||
minimumAvailability: PropTypes.object.isRequired,
|
||||
searchForMovie: PropTypes.object.isRequired,
|
||||
tags: PropTypes.object.isRequired,
|
||||
|
||||
@@ -6,7 +6,6 @@ import * as commandNames from 'Commands/commandNames';
|
||||
import withScrollPosition from 'Components/withScrollPosition';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import { saveMovieCollections, setMovieCollectionsFilter, setMovieCollectionsSort } from 'Store/Actions/movieCollectionActions';
|
||||
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
|
||||
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||
import scrollPositions from 'Store/scrollPositions';
|
||||
import createCollectionClientSideCollectionItemsSelector from 'Store/Selectors/createCollectionClientSideCollectionItemsSelector';
|
||||
@@ -39,12 +38,6 @@ function createMapDispatchToProps(dispatch, props) {
|
||||
dispatchFetchRootFolders() {
|
||||
dispatch(fetchRootFolders());
|
||||
},
|
||||
dispatchFetchQueueDetails() {
|
||||
dispatch(fetchQueueDetails());
|
||||
},
|
||||
dispatchClearQueueDetails() {
|
||||
dispatch(clearQueueDetails());
|
||||
},
|
||||
onUpdateSelectedPress(payload) {
|
||||
dispatch(saveMovieCollections(payload));
|
||||
},
|
||||
@@ -70,12 +63,10 @@ class CollectionConnector extends Component {
|
||||
componentDidMount() {
|
||||
registerPagePopulator(this.repopulate);
|
||||
this.props.dispatchFetchRootFolders();
|
||||
this.props.dispatchFetchQueueDetails();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
unregisterPagePopulator(this.repopulate);
|
||||
this.props.dispatchClearQueueDetails();
|
||||
}
|
||||
|
||||
//
|
||||
@@ -108,9 +99,7 @@ CollectionConnector.propTypes = {
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
view: PropTypes.string.isRequired,
|
||||
onUpdateSelectedPress: PropTypes.func.isRequired,
|
||||
dispatchFetchRootFolders: PropTypes.func.isRequired,
|
||||
dispatchFetchQueueDetails: PropTypes.func.isRequired,
|
||||
dispatchClearQueueDetails: PropTypes.func.isRequired
|
||||
dispatchFetchRootFolders: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default withScrollPosition(
|
||||
|
||||
@@ -14,50 +14,6 @@ import styles from './CollectionFooter.css';
|
||||
|
||||
const NO_CHANGE = 'noChange';
|
||||
|
||||
const monitoredOptions = [
|
||||
{
|
||||
key: NO_CHANGE,
|
||||
get value() {
|
||||
return translate('NoChange');
|
||||
},
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
key: 'monitored',
|
||||
get value() {
|
||||
return translate('Monitored');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'unmonitored',
|
||||
get value() {
|
||||
return translate('Unmonitored');
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const searchOnAddOptions = [
|
||||
{
|
||||
key: NO_CHANGE,
|
||||
get value() {
|
||||
return translate('NoChange');
|
||||
},
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
key: 'yes',
|
||||
get value() {
|
||||
return translate('Yes');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'no',
|
||||
get value() {
|
||||
return translate('No');
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
class CollectionFooter extends Component {
|
||||
|
||||
//
|
||||
@@ -67,12 +23,12 @@ class CollectionFooter extends Component {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
monitored: NO_CHANGE,
|
||||
monitor: NO_CHANGE,
|
||||
monitored: NO_CHANGE,
|
||||
qualityProfileId: NO_CHANGE,
|
||||
minimumAvailability: NO_CHANGE,
|
||||
rootFolderPath: NO_CHANGE,
|
||||
searchOnAdd: NO_CHANGE
|
||||
destinationRootFolder: null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -88,9 +44,8 @@ class CollectionFooter extends Component {
|
||||
monitored: NO_CHANGE,
|
||||
monitor: NO_CHANGE,
|
||||
qualityProfileId: NO_CHANGE,
|
||||
minimumAvailability: NO_CHANGE,
|
||||
rootFolderPath: NO_CHANGE,
|
||||
searchOnAdd: NO_CHANGE
|
||||
minimumAvailability: NO_CHANGE
|
||||
});
|
||||
}
|
||||
|
||||
@@ -108,12 +63,11 @@ class CollectionFooter extends Component {
|
||||
|
||||
onUpdateSelectedPress = () => {
|
||||
const {
|
||||
monitored,
|
||||
monitor,
|
||||
monitored,
|
||||
qualityProfileId,
|
||||
minimumAvailability,
|
||||
rootFolderPath,
|
||||
searchOnAdd
|
||||
rootFolderPath
|
||||
} = this.state;
|
||||
|
||||
const changes = {};
|
||||
@@ -138,10 +92,6 @@ class CollectionFooter extends Component {
|
||||
changes.rootFolderPath = rootFolderPath;
|
||||
}
|
||||
|
||||
if (searchOnAdd !== NO_CHANGE) {
|
||||
changes.searchOnAdd = searchOnAdd === 'yes';
|
||||
}
|
||||
|
||||
this.props.onUpdateSelectedPress(changes);
|
||||
};
|
||||
|
||||
@@ -159,10 +109,15 @@ class CollectionFooter extends Component {
|
||||
monitor,
|
||||
qualityProfileId,
|
||||
minimumAvailability,
|
||||
rootFolderPath,
|
||||
searchOnAdd
|
||||
rootFolderPath
|
||||
} = this.state;
|
||||
|
||||
const monitoredOptions = [
|
||||
{ key: NO_CHANGE, value: translate('NoChange'), disabled: true },
|
||||
{ key: 'monitored', value: translate('Monitored') },
|
||||
{ key: 'unmonitored', value: translate('Unmonitored') }
|
||||
];
|
||||
|
||||
const selectedCount = selectedIds.length;
|
||||
|
||||
return (
|
||||
@@ -170,7 +125,7 @@ class CollectionFooter extends Component {
|
||||
<div className={styles.inputContainer}>
|
||||
<CollectionFooterLabel
|
||||
label={translate('MonitorCollection')}
|
||||
isSaving={isSaving && monitored !== NO_CHANGE}
|
||||
isSaving={isSaving}
|
||||
/>
|
||||
|
||||
<SelectInput
|
||||
@@ -185,7 +140,7 @@ class CollectionFooter extends Component {
|
||||
<div className={styles.inputContainer}>
|
||||
<CollectionFooterLabel
|
||||
label={translate('MonitorMovies')}
|
||||
isSaving={isSaving && monitor !== NO_CHANGE}
|
||||
isSaving={isSaving}
|
||||
/>
|
||||
|
||||
<SelectInput
|
||||
@@ -243,25 +198,10 @@ class CollectionFooter extends Component {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.inputContainer}>
|
||||
<CollectionFooterLabel
|
||||
label={translate('SearchMoviesOnAdd')}
|
||||
isSaving={isSaving && searchOnAdd !== NO_CHANGE}
|
||||
/>
|
||||
|
||||
<SelectInput
|
||||
name="searchOnAdd"
|
||||
value={searchOnAdd}
|
||||
values={searchOnAddOptions}
|
||||
isDisabled={!selectedCount}
|
||||
onChange={this.onInputChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.buttonContainer}>
|
||||
<div className={styles.buttonContainerContent}>
|
||||
<CollectionFooterLabel
|
||||
label={translate('CountCollectionsSelected', { count: selectedCount })}
|
||||
label={translate('CollectionsSelectedInterp', [selectedCount])}
|
||||
isSaving={false}
|
||||
/>
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ function createMapStateToProps() {
|
||||
|
||||
return {
|
||||
...collection,
|
||||
movies: [...collection.movies].sort((a, b) => b.year - a.year),
|
||||
genres: Array.from(new Set(allGenres)).slice(0, 3)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ class EditCollectionModalContent extends Component {
|
||||
|
||||
const {
|
||||
monitored,
|
||||
qualityProfileId,
|
||||
qualityProfileIds,
|
||||
minimumAvailability,
|
||||
// Id,
|
||||
rootFolderPath,
|
||||
@@ -105,12 +105,12 @@ class EditCollectionModalContent extends Component {
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('QualityProfile')}</FormLabel>
|
||||
<FormLabel>{translate('QualityProfiles')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.QUALITY_PROFILE_SELECT}
|
||||
name="qualityProfileId"
|
||||
{...qualityProfileId}
|
||||
name="qualityProfileIds"
|
||||
{...qualityProfileIds}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
@@ -39,7 +39,7 @@ function createMapStateToProps() {
|
||||
|
||||
const movieSettings = {
|
||||
monitored: collection.monitored,
|
||||
qualityProfileId: collection.qualityProfileId,
|
||||
qualityProfileIds: collection.qualityProfileIds,
|
||||
minimumAvailability: collection.minimumAvailability,
|
||||
rootFolderPath: collection.rootFolderPath,
|
||||
tags: collection.tags,
|
||||
|
||||
@@ -61,7 +61,6 @@ class CollectionMovie extends Component {
|
||||
const {
|
||||
id,
|
||||
title,
|
||||
status,
|
||||
overview,
|
||||
year,
|
||||
tmdbId,
|
||||
@@ -70,7 +69,6 @@ class CollectionMovie extends Component {
|
||||
hasFile,
|
||||
folder,
|
||||
isAvailable,
|
||||
movieFile,
|
||||
isExistingMovie,
|
||||
posterWidth,
|
||||
posterHeight,
|
||||
@@ -125,15 +123,13 @@ class CollectionMovie extends Component {
|
||||
|
||||
<div className={styles.overlay}>
|
||||
<div className={styles.overlayTitle}>
|
||||
{title} {year > 0 ? `(${year})` : ''}
|
||||
{title}
|
||||
</div>
|
||||
|
||||
{
|
||||
id ?
|
||||
id &&
|
||||
<div className={styles.overlayStatus}>
|
||||
<MovieIndexProgressBar
|
||||
movieId={id}
|
||||
movieFile={movieFile}
|
||||
monitored={monitored}
|
||||
hasFile={hasFile}
|
||||
status={status}
|
||||
@@ -142,8 +138,7 @@ class CollectionMovie extends Component {
|
||||
detailedProgressBar={detailedProgressBar}
|
||||
isAvailable={isAvailable}
|
||||
/>
|
||||
</div> :
|
||||
null
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</Link>
|
||||
@@ -176,14 +171,12 @@ CollectionMovie.propTypes = {
|
||||
id: PropTypes.number,
|
||||
title: PropTypes.string.isRequired,
|
||||
year: PropTypes.number.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
overview: PropTypes.string.isRequired,
|
||||
monitored: PropTypes.bool,
|
||||
collectionId: PropTypes.number.isRequired,
|
||||
hasFile: PropTypes.bool,
|
||||
folder: PropTypes.string,
|
||||
isAvailable: PropTypes.bool,
|
||||
movieFile: PropTypes.object,
|
||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
posterWidth: PropTypes.number.isRequired,
|
||||
posterHeight: PropTypes.number.isRequired,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
margin: 2px 4px;
|
||||
border: 1px solid var(--borderColor);
|
||||
border-radius: 4px;
|
||||
background-color: var(--inputBackgroundColor);
|
||||
background-color: #eee;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
padding: 0 4px;
|
||||
border-left: 4px;
|
||||
border-left-style: solid;
|
||||
background-color: var(--themeLightColor);
|
||||
background-color: var(--white);
|
||||
color: var(--defaultColor);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,15 +14,16 @@ class CollectionMovieLabel extends Component {
|
||||
const {
|
||||
id,
|
||||
title,
|
||||
year,
|
||||
status,
|
||||
monitored,
|
||||
isAvailable,
|
||||
hasFile,
|
||||
onMonitorTogglePress,
|
||||
isSaving
|
||||
isSaving,
|
||||
statistics
|
||||
} = this.props;
|
||||
|
||||
const { movieFileCount } = statistics;
|
||||
|
||||
return (
|
||||
<div className={styles.movie}>
|
||||
<div className={styles.movieTitle}>
|
||||
@@ -36,7 +37,9 @@ class CollectionMovieLabel extends Component {
|
||||
}
|
||||
|
||||
<span>
|
||||
{title} {year > 0 ? `(${year})` : ''}
|
||||
{
|
||||
title
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -45,11 +48,11 @@ class CollectionMovieLabel extends Component {
|
||||
<div
|
||||
className={classNames(
|
||||
styles.movieStatus,
|
||||
styles[getStatusStyle(status, monitored, hasFile, isAvailable, 'kinds')]
|
||||
styles[getStatusStyle(status, monitored, movieFileCount > 0, isAvailable, 'kinds')]
|
||||
)}
|
||||
>
|
||||
{
|
||||
hasFile ? translate('Downloaded') : translate('Missing')
|
||||
movieFileCount > 0 ? translate('Downloaded') : translate('Missing')
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@@ -61,11 +64,10 @@ class CollectionMovieLabel extends Component {
|
||||
CollectionMovieLabel.propTypes = {
|
||||
id: PropTypes.number,
|
||||
title: PropTypes.string.isRequired,
|
||||
year: PropTypes.number.isRequired,
|
||||
status: PropTypes.string,
|
||||
statistics: PropTypes.object.isRequired,
|
||||
isAvailable: PropTypes.bool,
|
||||
monitored: PropTypes.bool,
|
||||
hasFile: PropTypes.bool,
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
movieFile: PropTypes.object,
|
||||
movieFileId: PropTypes.number,
|
||||
@@ -74,7 +76,9 @@ CollectionMovieLabel.propTypes = {
|
||||
|
||||
CollectionMovieLabel.defaultProps = {
|
||||
isSaving: false,
|
||||
statistics: {}
|
||||
statistics: {
|
||||
movieFileCount: 0
|
||||
}
|
||||
};
|
||||
|
||||
export default CollectionMovieLabel;
|
||||
|
||||
@@ -96,7 +96,7 @@ class CollectionOverview extends Component {
|
||||
render() {
|
||||
const {
|
||||
monitored,
|
||||
qualityProfileId,
|
||||
qualityProfileIds,
|
||||
rootFolderPath,
|
||||
genres,
|
||||
id,
|
||||
@@ -212,7 +212,7 @@ class CollectionOverview extends Component {
|
||||
<span className={styles.qualityProfileName}>
|
||||
{
|
||||
<QualityProfileNameConnector
|
||||
qualityProfileId={qualityProfileId}
|
||||
qualityProfileIds={qualityProfileIds}
|
||||
/>
|
||||
}
|
||||
</span>
|
||||
@@ -325,7 +325,7 @@ class CollectionOverview extends Component {
|
||||
CollectionOverview.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
qualityProfileId: PropTypes.number.isRequired,
|
||||
qualityProfileIds: PropTypes.number.isRequired,
|
||||
minimumAvailability: PropTypes.string.isRequired,
|
||||
searchOnAdd: PropTypes.bool.isRequired,
|
||||
rootFolderPath: PropTypes.string.isRequired,
|
||||
|
||||
@@ -28,6 +28,7 @@ function calculatePosterWidth(posterSize, isSmallScreen) {
|
||||
}
|
||||
|
||||
function calculateRowHeight(posterHeight, sortKey, isSmallScreen, overviewOptions) {
|
||||
|
||||
const heights = [
|
||||
overviewOptions.showPosters ? posterHeight : 75,
|
||||
isSmallScreen ? columnPaddingSmallScreen : columnPadding
|
||||
@@ -121,8 +122,8 @@ class CollectionOverviews extends Component {
|
||||
overviewOptions
|
||||
} = this.props;
|
||||
|
||||
const posterWidth = overviewOptions.showPosters ? calculatePosterWidth(overviewOptions.size, isSmallScreen) : 0;
|
||||
const posterHeight = overviewOptions.showPosters ? calculatePosterHeight(posterWidth) : 0;
|
||||
const posterWidth = calculatePosterWidth(overviewOptions.size, isSmallScreen);
|
||||
const posterHeight = calculatePosterHeight(posterWidth);
|
||||
const rowHeight = calculateRowHeight(posterHeight, sortKey, isSmallScreen, overviewOptions);
|
||||
|
||||
this.setState({
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
.description {
|
||||
line-height: $lineHeight;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-left: 0;
|
||||
line-height: $lineHeight;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
|
||||
@@ -6,12 +6,9 @@ import { filterBuilderTypes, filterBuilderValueTypes, icons } from 'Helpers/Prop
|
||||
import BoolFilterBuilderRowValue from './BoolFilterBuilderRowValue';
|
||||
import DateFilterBuilderRowValue from './DateFilterBuilderRowValue';
|
||||
import FilterBuilderRowValueConnector from './FilterBuilderRowValueConnector';
|
||||
import HistoryEventTypeFilterBuilderRowValue from './HistoryEventTypeFilterBuilderRowValue';
|
||||
import ImportListFilterBuilderRowValueConnector from './ImportListFilterBuilderRowValueConnector';
|
||||
import IndexerFilterBuilderRowValueConnector from './IndexerFilterBuilderRowValueConnector';
|
||||
import LanguageFilterBuilderRowValue from './LanguageFilterBuilderRowValue';
|
||||
import MinimumAvailabilityFilterBuilderRowValue from './MinimumAvailabilityFilterBuilderRowValue';
|
||||
import MovieFilterBuilderRowValue from './MovieFilterBuilderRowValue';
|
||||
import ProtocolFilterBuilderRowValue from './ProtocolFilterBuilderRowValue';
|
||||
import QualityFilterBuilderRowValueConnector from './QualityFilterBuilderRowValueConnector';
|
||||
import QualityProfileFilterBuilderRowValueConnector from './QualityProfileFilterBuilderRowValueConnector';
|
||||
@@ -61,15 +58,9 @@ function getRowValueConnector(selectedFilterBuilderProp) {
|
||||
case filterBuilderValueTypes.DATE:
|
||||
return DateFilterBuilderRowValue;
|
||||
|
||||
case filterBuilderValueTypes.HISTORY_EVENT_TYPE:
|
||||
return HistoryEventTypeFilterBuilderRowValue;
|
||||
|
||||
case filterBuilderValueTypes.INDEXER:
|
||||
return IndexerFilterBuilderRowValueConnector;
|
||||
|
||||
case filterBuilderValueTypes.LANGUAGE:
|
||||
return LanguageFilterBuilderRowValue;
|
||||
|
||||
case filterBuilderValueTypes.PROTOCOL:
|
||||
return ProtocolFilterBuilderRowValue;
|
||||
|
||||
@@ -79,9 +70,6 @@ function getRowValueConnector(selectedFilterBuilderProp) {
|
||||
case filterBuilderValueTypes.QUALITY_PROFILE:
|
||||
return QualityProfileFilterBuilderRowValueConnector;
|
||||
|
||||
case filterBuilderValueTypes.MOVIE:
|
||||
return MovieFilterBuilderRowValue;
|
||||
|
||||
case filterBuilderValueTypes.RELEASE_STATUS:
|
||||
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 {
|
||||
display: flex;
|
||||
height: 21px;
|
||||
|
||||
&.isLastTag {
|
||||
.or {
|
||||
@@ -18,5 +18,4 @@
|
||||
.or {
|
||||
margin: 0 3px;
|
||||
color: var(--themeDarkColor);
|
||||
line-height: 31px;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import styles from './FilterBuilderRowValueTag.css';
|
||||
|
||||
function FilterBuilderRowValueTag(props) {
|
||||
return (
|
||||
<div
|
||||
<span
|
||||
className={styles.tag}
|
||||
>
|
||||
<TagInputTag
|
||||
@@ -22,7 +22,7 @@ function FilterBuilderRowValueTag(props) {
|
||||
{translate('Or')}
|
||||
</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;
|
||||
@@ -30,24 +30,22 @@ function CustomFiltersModalContent(props) {
|
||||
|
||||
<ModalBody>
|
||||
{
|
||||
customFilters
|
||||
.sort((a, b) => a.label.localeCompare(b.label))
|
||||
.map((customFilter) => {
|
||||
return (
|
||||
<CustomFilter
|
||||
key={customFilter.id}
|
||||
id={customFilter.id}
|
||||
label={customFilter.label}
|
||||
filters={customFilter.filters}
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
isDeleting={isDeleting}
|
||||
deleteError={deleteError}
|
||||
dispatchSetFilter={dispatchSetFilter}
|
||||
dispatchDeleteCustomFilter={dispatchDeleteCustomFilter}
|
||||
onEditPress={onEditCustomFilter}
|
||||
/>
|
||||
);
|
||||
})
|
||||
customFilters.map((customFilter) => {
|
||||
return (
|
||||
<CustomFilter
|
||||
key={customFilter.id}
|
||||
id={customFilter.id}
|
||||
label={customFilter.label}
|
||||
filters={customFilter.filters}
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
isDeleting={isDeleting}
|
||||
deleteError={deleteError}
|
||||
dispatchSetFilter={dispatchSetFilter}
|
||||
dispatchDeleteCustomFilter={dispatchDeleteCustomFilter}
|
||||
onEditPress={onEditCustomFilter}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
<div className={styles.addButtonContainer}>
|
||||
|
||||
@@ -2,33 +2,19 @@ import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Button from 'Components/Link/Button';
|
||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import styles from './FormInputButton.css';
|
||||
|
||||
function FormInputButton(props) {
|
||||
const {
|
||||
className,
|
||||
canSpin,
|
||||
ButtonComponent,
|
||||
isLastButton,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
if (canSpin) {
|
||||
return (
|
||||
<SpinnerButton
|
||||
className={classNames(
|
||||
className,
|
||||
!isLastButton && styles.middleButton
|
||||
)}
|
||||
kind={kinds.PRIMARY}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
<ButtonComponent
|
||||
className={classNames(
|
||||
className,
|
||||
!isLastButton && styles.middleButton
|
||||
@@ -41,14 +27,14 @@ function FormInputButton(props) {
|
||||
|
||||
FormInputButton.propTypes = {
|
||||
className: PropTypes.string.isRequired,
|
||||
isLastButton: PropTypes.bool.isRequired,
|
||||
canSpin: PropTypes.bool.isRequired
|
||||
ButtonComponent: PropTypes.elementType.isRequired,
|
||||
isLastButton: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
FormInputButton.defaultProps = {
|
||||
className: styles.button,
|
||||
isLastButton: true,
|
||||
canSpin: false
|
||||
ButtonComponent: Button,
|
||||
isLastButton: true
|
||||
};
|
||||
|
||||
export default FormInputButton;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
.inputGroup {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.inputContainer {
|
||||
|
||||
@@ -21,6 +21,7 @@ import NumberInput from './NumberInput';
|
||||
import OAuthInputConnector from './OAuthInputConnector';
|
||||
import PasswordInput from './PasswordInput';
|
||||
import PathInputConnector from './PathInputConnector';
|
||||
import PlexMachineInputConnector from './PlexMachineInputConnector';
|
||||
import QualityProfileSelectInputConnector from './QualityProfileSelectInputConnector';
|
||||
import RootFolderSelectInputConnector from './RootFolderSelectInputConnector';
|
||||
import TagInputConnector from './TagInputConnector';
|
||||
@@ -63,6 +64,9 @@ function getComponent(type) {
|
||||
case inputTypes.PATH:
|
||||
return PathInputConnector;
|
||||
|
||||
case inputTypes.PLEX_MACHINE_SELECT:
|
||||
return PlexMachineInputConnector;
|
||||
|
||||
case inputTypes.QUALITY_PROFILE_SELECT:
|
||||
return QualityProfileSelectInputConnector;
|
||||
|
||||
@@ -267,7 +271,6 @@ FormInputGroup.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.any,
|
||||
values: PropTypes.arrayOf(PropTypes.any),
|
||||
isDisabled: PropTypes.bool,
|
||||
type: PropTypes.string.isRequired,
|
||||
kind: PropTypes.oneOf(kinds.all),
|
||||
min: PropTypes.number,
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-right: $formLabelRightMarginWidth;
|
||||
padding-top: 8px;
|
||||
min-height: 35px;
|
||||
text-align: end;
|
||||
font-weight: bold;
|
||||
line-height: 35px;
|
||||
}
|
||||
|
||||
.hasError {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { kinds } from 'Helpers/Props';
|
||||
|
||||
function OAuthInput(props) {
|
||||
const {
|
||||
className,
|
||||
label,
|
||||
authorizing,
|
||||
error,
|
||||
@@ -12,21 +13,21 @@ function OAuthInput(props) {
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SpinnerErrorButton
|
||||
kind={kinds.PRIMARY}
|
||||
isSpinning={authorizing}
|
||||
error={error}
|
||||
onPress={onPress}
|
||||
>
|
||||
{label}
|
||||
</SpinnerErrorButton>
|
||||
</div>
|
||||
<SpinnerErrorButton
|
||||
className={className}
|
||||
kind={kinds.PRIMARY}
|
||||
isSpinning={authorizing}
|
||||
error={error}
|
||||
onPress={onPress}
|
||||
>
|
||||
{label}
|
||||
</SpinnerErrorButton>
|
||||
);
|
||||
}
|
||||
|
||||
OAuthInput.propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
className: PropTypes.string,
|
||||
label: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
|
||||
authorizing: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
onPress: PropTypes.func.isRequired
|
||||
|
||||
44
frontend/src/Components/Form/PlexMachineInput.js
Normal file
44
frontend/src/Components/Form/PlexMachineInput.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import SelectInput from './SelectInput';
|
||||
|
||||
function PlexMachineInput(props) {
|
||||
const {
|
||||
isFetching,
|
||||
isDisabled,
|
||||
value,
|
||||
values,
|
||||
onChange,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
const helpText = 'Authenticate with plex.tv to show servers to use for authentication';
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
isFetching ?
|
||||
<LoadingIndicator /> :
|
||||
<SelectInput
|
||||
value={value}
|
||||
values={values}
|
||||
isDisabled={isDisabled}
|
||||
onChange={onChange}
|
||||
helpText={helpText}
|
||||
{...otherProps}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
PlexMachineInput.propTypes = {
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isDisabled: PropTypes.bool.isRequired,
|
||||
value: PropTypes.string,
|
||||
values: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default PlexMachineInput;
|
||||
115
frontend/src/Components/Form/PlexMachineInputConnector.js
Normal file
115
frontend/src/Components/Form/PlexMachineInputConnector.js
Normal file
@@ -0,0 +1,115 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { fetchPlexResources } from 'Store/Actions/settingsActions';
|
||||
import PlexMachineInput from './PlexMachineInput';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state, { value }) => value,
|
||||
(state) => state.oAuth,
|
||||
(state) => state.settings.plex,
|
||||
(value, oAuth, plex) => {
|
||||
|
||||
let values = [{ key: value, value }];
|
||||
let isDisabled = true;
|
||||
|
||||
if (plex.isPopulated) {
|
||||
const serverValues = plex.items.filter((item) => item.provides.includes('server')).map((item) => {
|
||||
return ({
|
||||
key: item.clientIdentifier,
|
||||
value: `${item.name} / ${item.owned ? 'Owner' : 'User'} / ${item.clientIdentifier}`
|
||||
});
|
||||
});
|
||||
|
||||
if (serverValues.find((item) => item.key === value)) {
|
||||
values = serverValues;
|
||||
} else {
|
||||
values = values.concat(serverValues);
|
||||
}
|
||||
|
||||
isDisabled = false;
|
||||
}
|
||||
|
||||
return ({
|
||||
accessToken: oAuth.result?.accessToken,
|
||||
values,
|
||||
isDisabled,
|
||||
...plex
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
dispatchFetchPlexResources: fetchPlexResources
|
||||
};
|
||||
|
||||
class PlexMachineInputConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
componentDidMount = () => {
|
||||
const {
|
||||
accessToken,
|
||||
dispatchFetchPlexResources
|
||||
} = this.props;
|
||||
|
||||
if (accessToken) {
|
||||
dispatchFetchPlexResources({ accessToken });
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
accessToken,
|
||||
dispatchFetchPlexResources
|
||||
} = this.props;
|
||||
|
||||
const oldToken = prevProps.accessToken;
|
||||
if (accessToken && accessToken !== oldToken) {
|
||||
dispatchFetchPlexResources({ accessToken });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
isDisabled,
|
||||
value,
|
||||
values,
|
||||
onChange
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<PlexMachineInput
|
||||
isFetching={isFetching}
|
||||
isPopulated={isPopulated}
|
||||
isDisabled={isDisabled}
|
||||
value={value}
|
||||
values={values}
|
||||
onChange={onChange}
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PlexMachineInputConnector.propTypes = {
|
||||
dispatchFetchPlexResources: PropTypes.func.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
values: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
isDisabled: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
oAuth: PropTypes.object,
|
||||
accessToken: PropTypes.string,
|
||||
onChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(PlexMachineInputConnector);
|
||||
@@ -37,8 +37,6 @@ function getType({ type, selectOptionsProviderAction }) {
|
||||
return inputTypes.OAUTH;
|
||||
case 'rootFolder':
|
||||
return inputTypes.ROOT_FOLDER_SELECT;
|
||||
case 'qualityProfile':
|
||||
return inputTypes.QUALITY_PROFILE_SELECT;
|
||||
default:
|
||||
return inputTypes.TEXT;
|
||||
}
|
||||
|
||||
@@ -47,32 +47,6 @@ function createMapStateToProps() {
|
||||
|
||||
class QualityProfileSelectInputConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
name,
|
||||
value,
|
||||
values
|
||||
} = this.props;
|
||||
|
||||
if (!value || !values.some((option) => option.key === value || parseInt(option.key) === value)) {
|
||||
const firstValue = values.find((option) => !isNaN(parseInt(option.key)));
|
||||
|
||||
if (firstValue) {
|
||||
this.onChange({ name, value: firstValue.key });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onChange = ({ name, value }) => {
|
||||
this.props.onChange({ name, value: value === 'noChange' ? value : parseInt(value) });
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
@@ -80,7 +54,7 @@ class QualityProfileSelectInputConnector extends Component {
|
||||
return (
|
||||
<EnhancedSelectInput
|
||||
{...this.props}
|
||||
onChange={this.onChange}
|
||||
onChange={this.props.onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -88,7 +62,7 @@ class QualityProfileSelectInputConnector extends Component {
|
||||
|
||||
QualityProfileSelectInputConnector.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
value: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.number), PropTypes.arrayOf(PropTypes.string)]),
|
||||
values: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
includeNoChange: PropTypes.bool.isRequired,
|
||||
onChange: PropTypes.func.isRequired
|
||||
|
||||
@@ -3,7 +3,6 @@ import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { addRootFolder } from 'Store/Actions/rootFolderActions';
|
||||
import createRootFoldersSelector from 'Store/Selectors/createRootFoldersSelector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import RootFolderSelectInput from './RootFolderSelectInput';
|
||||
|
||||
@@ -11,7 +10,7 @@ const ADD_NEW_KEY = 'addNew';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createRootFoldersSelector(),
|
||||
(state) => state.rootFolders,
|
||||
(state, { value }) => value,
|
||||
(state, { includeMissingValue }) => includeMissingValue,
|
||||
(state, { includeNoChange }) => includeNoChange,
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
composes: hasWarning from '~Components/Form/Input.css';
|
||||
}
|
||||
|
||||
.hasButton {
|
||||
composes: hasButton from '~Components/Form/Input.css';
|
||||
}
|
||||
|
||||
.isDisabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'hasButton': string;
|
||||
'hasError': string;
|
||||
'hasWarning': string;
|
||||
'isDisabled': string;
|
||||
|
||||
@@ -28,6 +28,7 @@ class SelectInput extends Component {
|
||||
isDisabled,
|
||||
hasError,
|
||||
hasWarning,
|
||||
hasButton,
|
||||
autoFocus,
|
||||
onBlur
|
||||
} = this.props;
|
||||
@@ -38,6 +39,7 @@ class SelectInput extends Component {
|
||||
className,
|
||||
hasError && styles.hasError,
|
||||
hasWarning && styles.hasWarning,
|
||||
hasButton && styles.hasButton,
|
||||
isDisabled && disabledClassName
|
||||
)}
|
||||
disabled={isDisabled}
|
||||
@@ -80,6 +82,7 @@ SelectInput.propTypes = {
|
||||
isDisabled: PropTypes.bool,
|
||||
hasError: PropTypes.bool,
|
||||
hasWarning: PropTypes.bool,
|
||||
hasButton: PropTypes.bool,
|
||||
autoFocus: PropTypes.bool.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onBlur: PropTypes.func
|
||||
|
||||
@@ -12,10 +12,18 @@
|
||||
|
||||
.info {
|
||||
color: var(--infoColor);
|
||||
|
||||
&:global(.darken) {
|
||||
color: color(var(--infoColor) shade(30%));
|
||||
}
|
||||
}
|
||||
|
||||
.pink {
|
||||
color: var(--pink);
|
||||
|
||||
&:global(.darken) {
|
||||
color: color(var(--pink) shade(30%));
|
||||
}
|
||||
}
|
||||
|
||||
.success {
|
||||
|
||||
@@ -18,6 +18,7 @@ class Icon extends PureComponent {
|
||||
kind,
|
||||
size,
|
||||
title,
|
||||
darken,
|
||||
isSpinning,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
@@ -26,7 +27,8 @@ class Icon extends PureComponent {
|
||||
<FontAwesomeIcon
|
||||
className={classNames(
|
||||
className,
|
||||
styles[kind]
|
||||
styles[kind],
|
||||
darken && 'darken'
|
||||
)}
|
||||
icon={name}
|
||||
spin={isSpinning}
|
||||
@@ -59,6 +61,7 @@ Icon.propTypes = {
|
||||
kind: PropTypes.string.isRequired,
|
||||
size: PropTypes.number.isRequired,
|
||||
title: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
||||
darken: PropTypes.bool.isRequired,
|
||||
isSpinning: PropTypes.bool.isRequired,
|
||||
fixedWidth: PropTypes.bool.isRequired
|
||||
};
|
||||
@@ -66,6 +69,7 @@ Icon.propTypes = {
|
||||
Icon.defaultProps = {
|
||||
kind: kinds.DEFAULT,
|
||||
size: 14,
|
||||
darken: false,
|
||||
isSpinning: false,
|
||||
fixedWidth: false
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ function ImportListList({ lists, importListList }) {
|
||||
return (
|
||||
<Label
|
||||
key={list.id}
|
||||
kind={kinds.SUCCESS}
|
||||
kind={kinds.INFO}
|
||||
size={sizes.MEDIUM}
|
||||
>
|
||||
{list.name}
|
||||
|
||||
@@ -40,26 +40,18 @@ class FilterMenuContent extends Component {
|
||||
}
|
||||
|
||||
{
|
||||
customFilters.length > 0 ?
|
||||
<MenuItemSeparator /> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
customFilters
|
||||
.sort((a, b) => a.label.localeCompare(b.label))
|
||||
.map((filter) => {
|
||||
return (
|
||||
<FilterMenuItem
|
||||
key={filter.id}
|
||||
filterKey={filter.id}
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
onPress={onFilterSelect}
|
||||
>
|
||||
{filter.label}
|
||||
</FilterMenuItem>
|
||||
);
|
||||
})
|
||||
customFilters.map((filter) => {
|
||||
return (
|
||||
<FilterMenuItem
|
||||
key={filter.id}
|
||||
filterKey={filter.id}
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
onPress={onFilterSelect}
|
||||
>
|
||||
{filter.label}
|
||||
</FilterMenuItem>
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user