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

Compare commits

..

3 Commits

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

View File

@@ -1,13 +0,0 @@
// This file is used to open the backend and frontend in the same workspace, which is necessary as
// the frontend has vscode settings that are distinct from the backend
{
"folders": [
{
"path": ".."
},
{
"path": "../frontend"
}
],
"settings": {}
}

View File

@@ -1,19 +0,0 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet
{
"name": "Radarr",
"image": "mcr.microsoft.com/devcontainers/dotnet:1-6.0",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"nodeGypDependencies": true,
"version": "16",
"nvmVersion": "latest"
}
},
"forwardPorts": [7878],
"customizations": {
"vscode": {
"extensions": ["esbenp.prettier-vscode"]
}
}
}

View File

@@ -1,12 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for more information:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
# https://containers.dev/guide/dependabot
version: 2
updates:
- package-ecosystem: "devcontainers"
directory: "/"
schedule:
interval: weekly

1
.gitignore vendored
View File

@@ -126,7 +126,6 @@ coverage*.xml
coverage*.json coverage*.json
setup/Output/ setup/Output/
*.~is *.~is
.mono
# VS outout folders # VS outout folders
bin bin

View File

@@ -1,7 +0,0 @@
{
"recommendations": [
"esbenp.prettier-vscode",
"ms-dotnettools.csdevkit",
"ms-vscode-remote.remote-containers"
]
}

26
.vscode/launch.json vendored
View File

@@ -1,26 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md
"name": "Run Radarr",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build dotnet",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/_output/net6.0/Radarr",
"args": [],
"cwd": "${workspaceFolder}",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
"console": "integratedTerminal",
"stopAtEntry": false
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}

44
.vscode/tasks.json vendored
View File

@@ -1,44 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build dotnet",
"command": "dotnet",
"type": "process",
"args": [
"msbuild",
"-restore",
"${workspaceFolder}/src/Radarr.sln",
"-p:GenerateFullPaths=true",
"-p:Configuration=Debug",
"-p:Platform=Posix",
"-consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/src/Radarr.sln",
"-property:GenerateFullPaths=true",
"-consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/src/Radarr.sln"
],
"problemMatcher": "$msCompile"
}
]
}

View File

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

View File

@@ -9,15 +9,15 @@ variables:
testsFolder: './_tests' testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '5.5.2' majorVersion: '5.0.3'
minorVersion: $[counter('minorVersion', 2000)] minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)' radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)' buildName: '$(Build.SourceBranchName).$(radarrVersion)'
sentryOrg: 'servarr' sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com' sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.421' dotnetVersion: '6.0.413'
nodeVersion: '20.X' nodeVersion: '16.X'
innoVersion: '6.2.2' innoVersion: '6.2.0'
windowsImage: 'windows-2022' windowsImage: 'windows-2022'
linuxImage: 'ubuntu-20.04' linuxImage: 'ubuntu-20.04'
macImage: 'macOS-11' macImage: 'macOS-11'
@@ -166,10 +166,10 @@ stages:
pool: pool:
vmImage: $(imageName) vmImage: $(imageName)
steps: steps:
- task: UseNode@1 - task: NodeTool@0
displayName: Set Node.js version displayName: Set Node.js version
inputs: inputs:
version: $(nodeVersion) versionSpec: $(nodeVersion)
- checkout: self - checkout: self
submodules: true submodules: true
fetchDepth: 1 fetchDepth: 1
@@ -1089,10 +1089,10 @@ stages:
pool: pool:
vmImage: $(imageName) vmImage: $(imageName)
steps: steps:
- task: UseNode@1 - task: NodeTool@0
displayName: Set Node.js version displayName: Set Node.js version
inputs: inputs:
version: $(nodeVersion) versionSpec: $(nodeVersion)
- checkout: self - checkout: self
submodules: true submodules: true
fetchDepth: 1 fetchDepth: 1
@@ -1242,7 +1242,6 @@ stages:
- stage: Report_Out - stage: Report_Out
dependsOn: dependsOn:
- Analyze - Analyze
- Installer
- Unit_Test - Unit_Test
- Integration - Integration
- Automation - Automation

View File

@@ -254,7 +254,7 @@ InstallInno()
ProgressStart "Installing portable Inno Setup" ProgressStart "Installing portable Inno Setup"
rm -rf _inno rm -rf _inno
curl -s --output innosetup.exe "https://files.jrsoftware.org/is/6/innosetup-${INNOVERSION:-6.2.2}.exe" curl -s --output innosetup.exe "https://files.jrsoftware.org/is/6/innosetup-${INNOVERSION:-6.2.0}.exe"
mkdir _inno mkdir _inno
./innosetup.exe //portable=1 //silent //currentuser //dir=.\\_inno ./innosetup.exe //portable=1 //silent //currentuser //dir=.\\_inno
rm innosetup.exe rm innosetup.exe

View File

@@ -2,8 +2,6 @@ const loose = true;
module.exports = { module.exports = {
plugins: [ plugins: [
'@babel/plugin-transform-logical-assignment-operators',
// Stage 1 // Stage 1
'@babel/plugin-proposal-export-default-from', '@babel/plugin-proposal-export-default-from',
['@babel/plugin-transform-optional-chaining', { loose }], ['@babel/plugin-transform-optional-chaining', { loose }],

View File

@@ -36,7 +36,6 @@ class Blocklist extends Component {
lastToggled: null, lastToggled: null,
selectedState: {}, selectedState: {},
isConfirmRemoveModalOpen: false, isConfirmRemoveModalOpen: false,
isConfirmClearModalOpen: false,
items: props.items items: props.items
}; };
} }
@@ -91,19 +90,6 @@ class Blocklist extends Component {
this.setState({ isConfirmRemoveModalOpen: false }); this.setState({ isConfirmRemoveModalOpen: false });
}; };
onClearBlocklistPress = () => {
this.setState({ isConfirmClearModalOpen: true });
};
onClearBlocklistConfirmed = () => {
this.props.onClearBlocklistPress();
this.setState({ isConfirmClearModalOpen: false });
};
onConfirmClearModalClose = () => {
this.setState({ isConfirmClearModalOpen: false });
};
// //
// Render // Render
@@ -117,6 +103,7 @@ class Blocklist extends Component {
totalRecords, totalRecords,
isRemoving, isRemoving,
isClearingBlocklistExecuting, isClearingBlocklistExecuting,
onClearBlocklistPress,
...otherProps ...otherProps
} = this.props; } = this.props;
@@ -124,8 +111,7 @@ class Blocklist extends Component {
allSelected, allSelected,
allUnselected, allUnselected,
selectedState, selectedState,
isConfirmRemoveModalOpen, isConfirmRemoveModalOpen
isConfirmClearModalOpen
} = this.state; } = this.state;
const selectedIds = this.getSelectedIds(); const selectedIds = this.getSelectedIds();
@@ -145,9 +131,8 @@ class Blocklist extends Component {
<PageToolbarButton <PageToolbarButton
label={translate('Clear')} label={translate('Clear')}
iconName={icons.CLEAR} iconName={icons.CLEAR}
isDisabled={!items.length}
isSpinning={isClearingBlocklistExecuting} isSpinning={isClearingBlocklistExecuting}
onPress={this.onClearBlocklistPress} onPress={onClearBlocklistPress}
/> />
</PageToolbarSection> </PageToolbarSection>
@@ -230,16 +215,6 @@ class Blocklist extends Component {
onConfirm={this.onRemoveSelectedConfirmed} onConfirm={this.onRemoveSelectedConfirmed}
onCancel={this.onConfirmRemoveModalClose} onCancel={this.onConfirmRemoveModalClose}
/> />
<ConfirmModal
isOpen={isConfirmClearModalOpen}
kind={kinds.DANGER}
title={translate('ClearBlocklist')}
message={translate('ClearBlocklistMessageText')}
confirmLabel={translate('Clear')}
onConfirm={this.onClearBlocklistConfirmed}
onCancel={this.onConfirmClearModalClose}
/>
</PageContent> </PageContent>
); );
} }

View File

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

View File

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

View File

@@ -6,7 +6,7 @@ import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import styles from './HistoryEventTypeCell.css'; import styles from './HistoryEventTypeCell.css';
function getIconName(eventType, data) { function getIconName(eventType) {
switch (eventType) { switch (eventType) {
case 'grabbed': case 'grabbed':
return icons.DOWNLOADING; return icons.DOWNLOADING;
@@ -17,7 +17,7 @@ function getIconName(eventType, data) {
case 'downloadFailed': case 'downloadFailed':
return icons.DOWNLOADING; return icons.DOWNLOADING;
case 'movieFileDeleted': case 'movieFileDeleted':
return data.reason === 'MissingFromDisk' ? icons.FILE_MISSING : icons.DELETE; return icons.DELETE;
case 'movieFileRenamed': case 'movieFileRenamed':
return icons.ORGANIZE; return icons.ORGANIZE;
case 'downloadIgnored': case 'downloadIgnored':
@@ -47,7 +47,7 @@ function getTooltip(eventType, data) {
case 'downloadFailed': case 'downloadFailed':
return translate('MovieDownloadFailedTooltip'); return translate('MovieDownloadFailedTooltip');
case 'movieFileDeleted': case 'movieFileDeleted':
return data.reason === 'MissingFromDisk' ? translate('MovieFileMissingTooltip') : translate('MovieFileDeletedTooltip'); return translate('MovieFileDeletedTooltip');
case 'movieFileRenamed': case 'movieFileRenamed':
return translate('MovieFileRenamedTooltip'); return translate('MovieFileRenamedTooltip');
case 'downloadIgnored': case 'downloadIgnored':
@@ -58,7 +58,7 @@ function getTooltip(eventType, data) {
} }
function HistoryEventTypeCell({ eventType, data }) { function HistoryEventTypeCell({ eventType, data }) {
const iconName = getIconName(eventType, data); const iconName = getIconName(eventType);
const iconKind = getIconKind(eventType); const iconKind = getIconKind(eventType);
const tooltip = getTooltip(eventType, data); const tooltip = getTooltip(eventType, data);

View File

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

View File

@@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Alert from 'Components/Alert'; import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import FilterMenu from 'Components/Menu/FilterMenu';
import PageContent from 'Components/Page/PageContent'; import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody'; import PageContentBody from 'Components/Page/PageContentBody';
import PageToolbar from 'Components/Page/Toolbar/PageToolbar'; import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
@@ -22,10 +21,9 @@ import getSelectedIds from 'Utilities/Table/getSelectedIds';
import removeOldSelectedState from 'Utilities/Table/removeOldSelectedState'; import removeOldSelectedState from 'Utilities/Table/removeOldSelectedState';
import selectAll from 'Utilities/Table/selectAll'; import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected'; import toggleSelected from 'Utilities/Table/toggleSelected';
import QueueFilterModal from './QueueFilterModal';
import QueueOptionsConnector from './QueueOptionsConnector'; import QueueOptionsConnector from './QueueOptionsConnector';
import QueueRowConnector from './QueueRowConnector'; import QueueRowConnector from './QueueRowConnector';
import RemoveQueueItemModal from './RemoveQueueItemModal'; import RemoveQueueItemsModal from './RemoveQueueItemsModal';
class Queue extends Component { class Queue extends Component {
@@ -155,16 +153,11 @@ class Queue extends Component {
isMoviesPopulated, isMoviesPopulated,
moviesError, moviesError,
columns, columns,
selectedFilterKey,
filters,
customFilters,
count,
totalRecords, totalRecords,
isGrabbing, isGrabbing,
isRemoving, isRemoving,
isRefreshMonitoredDownloadsExecuting, isRefreshMonitoredDownloadsExecuting,
onRefreshPress, onRefreshPress,
onFilterSelect,
...otherProps ...otherProps
} = this.props; } = this.props;
@@ -227,15 +220,6 @@ class Queue extends Component {
iconName={icons.TABLE} iconName={icons.TABLE}
/> />
</TableOptionsModalWrapper> </TableOptionsModalWrapper>
<FilterMenu
alignMenu={align.RIGHT}
selectedFilterKey={selectedFilterKey}
filters={filters}
customFilters={customFilters}
filterModalConnectorComponent={QueueFilterModal}
onFilterSelect={onFilterSelect}
/>
</PageToolbarSection> </PageToolbarSection>
</PageToolbar> </PageToolbar>
@@ -257,11 +241,7 @@ class Queue extends Component {
{ {
isAllPopulated && !hasError && !items.length ? isAllPopulated && !hasError && !items.length ?
<Alert kind={kinds.INFO}> <Alert kind={kinds.INFO}>
{ {translate('QueueIsEmpty')}
selectedFilterKey !== 'all' && count > 0 ?
translate('QueueFilterHasNoItems') :
translate('QueueIsEmpty')
}
</Alert> : </Alert> :
null null
} }
@@ -307,16 +287,9 @@ class Queue extends Component {
} }
</PageContentBody> </PageContentBody>
<RemoveQueueItemModal <RemoveQueueItemsModal
isOpen={isConfirmRemoveModalOpen} isOpen={isConfirmRemoveModalOpen}
selectedCount={selectedCount} selectedCount={selectedCount}
canChangeCategory={isConfirmRemoveModalOpen && (
selectedIds.every((id) => {
const item = items.find((i) => i.id === id);
return !!(item && item.downloadClientHasPostImportCategory);
})
)}
canIgnore={isConfirmRemoveModalOpen && ( canIgnore={isConfirmRemoveModalOpen && (
selectedIds.every((id) => { selectedIds.every((id) => {
const item = items.find((i) => i.id === id); const item = items.find((i) => i.id === id);
@@ -324,7 +297,7 @@ class Queue extends Component {
return !!(item && item.movieId); return !!(item && item.movieId);
}) })
)} )}
pending={isConfirmRemoveModalOpen && ( allPending={isConfirmRemoveModalOpen && (
selectedIds.every((id) => { selectedIds.every((id) => {
const item = items.find((i) => i.id === id); const item = items.find((i) => i.id === id);
@@ -352,22 +325,13 @@ Queue.propTypes = {
moviesError: PropTypes.object, moviesError: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired,
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
count: PropTypes.number.isRequired,
totalRecords: PropTypes.number, totalRecords: PropTypes.number,
isGrabbing: PropTypes.bool.isRequired, isGrabbing: PropTypes.bool.isRequired,
isRemoving: PropTypes.bool.isRequired, isRemoving: PropTypes.bool.isRequired,
isRefreshMonitoredDownloadsExecuting: PropTypes.bool.isRequired, isRefreshMonitoredDownloadsExecuting: PropTypes.bool.isRequired,
onRefreshPress: PropTypes.func.isRequired, onRefreshPress: PropTypes.func.isRequired,
onGrabSelectedPress: PropTypes.func.isRequired, onGrabSelectedPress: PropTypes.func.isRequired,
onRemoveSelectedPress: PropTypes.func.isRequired, onRemoveSelectedPress: PropTypes.func.isRequired
onFilterSelect: PropTypes.func.isRequired
};
Queue.defaultProps = {
count: 0
}; };
export default Queue; export default Queue;

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
import IconButton from 'Components/Link/IconButton'; import IconButton from 'Components/Link/IconButton';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton'; import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import ProgressBar from 'Components/ProgressBar'; import ProgressBar from 'Components/ProgressBar';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector'; // import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell'; import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import TableRow from 'Components/Table/TableRow'; import TableRow from 'Components/Table/TableRow';
@@ -96,9 +96,7 @@ class QueueRow extends Component {
indexer, indexer,
outputPath, outputPath,
downloadClient, downloadClient,
downloadClientHasPostImportCategory,
estimatedCompletionTime, estimatedCompletionTime,
added,
timeleft, timeleft,
size, size,
sizeleft, sizeleft,
@@ -317,15 +315,6 @@ class QueueRow extends Component {
); );
} }
if (name === 'added') {
return (
<RelativeDateCellConnector
key={name}
date={added}
/>
);
}
if (name === 'actions') { if (name === 'actions') {
return ( return (
<TableRowCell <TableRowCell
@@ -374,7 +363,6 @@ class QueueRow extends Component {
<RemoveQueueItemModal <RemoveQueueItemModal
isOpen={isRemoveQueueItemModalOpen} isOpen={isRemoveQueueItemModalOpen}
sourceTitle={title} sourceTitle={title}
canChangeCategory={!!downloadClientHasPostImportCategory}
canIgnore={!!movie} canIgnore={!!movie}
isPending={isPending} isPending={isPending}
onRemovePress={this.onRemoveQueueItemModalConfirmed} onRemovePress={this.onRemoveQueueItemModalConfirmed}
@@ -404,9 +392,7 @@ QueueRow.propTypes = {
indexer: PropTypes.string, indexer: PropTypes.string,
outputPath: PropTypes.string, outputPath: PropTypes.string,
downloadClient: PropTypes.string, downloadClient: PropTypes.string,
downloadClientHasPostImportCategory: PropTypes.bool,
estimatedCompletionTime: PropTypes.string, estimatedCompletionTime: PropTypes.string,
added: PropTypes.string,
timeleft: PropTypes.string, timeleft: PropTypes.string,
size: PropTypes.number, size: PropTypes.number,
year: PropTypes.number, year: PropTypes.number,

View File

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

View File

@@ -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;

View File

@@ -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;

View 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;

View File

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

View File

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

View File

@@ -3,13 +3,10 @@ import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { clearAddMovie, lookupMovie } from 'Store/Actions/addMovieActions'; import { clearAddMovie, lookupMovie } from 'Store/Actions/addMovieActions';
import { clearMovieFiles, fetchMovieFiles } from 'Store/Actions/movieFileActions';
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions'; import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
import { fetchRootFolders } from 'Store/Actions/rootFolderActions'; import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
import { fetchImportExclusions } from 'Store/Actions/Settings/importExclusions'; import { fetchImportExclusions } from 'Store/Actions/Settings/importExclusions';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector'; 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 parseUrl from 'Utilities/String/parseUrl';
import AddNewMovie from './AddNewMovie'; import AddNewMovie from './AddNewMovie';
@@ -38,9 +35,7 @@ const mapDispatchToProps = {
fetchRootFolders, fetchRootFolders,
fetchImportExclusions, fetchImportExclusions,
fetchQueueDetails, fetchQueueDetails,
clearQueueDetails, clearQueueDetails
fetchMovieFiles,
clearMovieFiles
}; };
class AddNewMovieConnector extends Component { class AddNewMovieConnector extends Component {
@@ -60,20 +55,6 @@ class AddNewMovieConnector extends Component {
this.props.fetchQueueDetails(); 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() { componentWillUnmount() {
if (this._movieLookupTimeout) { if (this._movieLookupTimeout) {
clearTimeout(this._movieLookupTimeout); clearTimeout(this._movieLookupTimeout);
@@ -81,7 +62,6 @@ class AddNewMovieConnector extends Component {
this.props.clearAddMovie(); this.props.clearAddMovie();
this.props.clearQueueDetails(); this.props.clearQueueDetails();
this.props.clearMovieFiles();
} }
// //
@@ -127,15 +107,12 @@ class AddNewMovieConnector extends Component {
AddNewMovieConnector.propTypes = { AddNewMovieConnector.propTypes = {
term: PropTypes.string, term: PropTypes.string,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
lookupMovie: PropTypes.func.isRequired, lookupMovie: PropTypes.func.isRequired,
clearAddMovie: PropTypes.func.isRequired, clearAddMovie: PropTypes.func.isRequired,
fetchRootFolders: PropTypes.func.isRequired, fetchRootFolders: PropTypes.func.isRequired,
fetchImportExclusions: PropTypes.func.isRequired, fetchImportExclusions: PropTypes.func.isRequired,
fetchQueueDetails: PropTypes.func.isRequired, fetchQueueDetails: PropTypes.func.isRequired,
clearQueueDetails: PropTypes.func.isRequired, clearQueueDetails: PropTypes.func.isRequired
fetchMovieFiles: PropTypes.func.isRequired,
clearMovieFiles: PropTypes.func.isRequired
}; };
export default connect(createMapStateToProps, mapDispatchToProps)(AddNewMovieConnector); export default connect(createMapStateToProps, mapDispatchToProps)(AddNewMovieConnector);

View File

@@ -85,13 +85,8 @@
margin-top: 20px; margin-top: 20px;
} }
.studio,
.genres {
margin-left: 5px;
}
.links { .links {
margin-left: 5px; margin-left: 8px;
pointer-events: all; pointer-events: all;
} }

View File

@@ -5,7 +5,6 @@ interface CssExports {
'certification': string; 'certification': string;
'content': string; 'content': string;
'exclusionIcon': string; 'exclusionIcon': string;
'genres': string;
'icons': string; 'icons': string;
'links': string; 'links': string;
'overlay': string; 'overlay': string;
@@ -15,7 +14,6 @@ interface CssExports {
'runtime': string; 'runtime': string;
'searchResult': string; 'searchResult': string;
'statusContainer': string; 'statusContainer': string;
'studio': string;
'title': string; 'title': string;
'titleContainer': string; 'titleContainer': string;
'titleRow': string; 'titleRow': string;

View File

@@ -1,7 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import ImdbRating from 'Components/ImdbRating';
import Label from 'Components/Label'; import Label from 'Components/Label';
import Link from 'Components/Link/Link'; import Link from 'Components/Link/Link';
import TmdbRating from 'Components/TmdbRating'; import TmdbRating from 'Components/TmdbRating';
@@ -62,22 +61,21 @@ class AddNewMovieSearchResult extends Component {
titleSlug, titleSlug,
year, year,
studio, studio,
genres,
status, status,
overview, overview,
ratings, ratings,
folder, folder,
images, images,
existingMovieId,
isExistingMovie, isExistingMovie,
isExclusionMovie, isExclusionMovie,
isSmallScreen, isSmallScreen,
colorImpairedMode, colorImpairedMode,
id, id,
monitored, monitored,
hasFile,
isAvailable, isAvailable,
movieFile, queueStatus,
queueItem, queueState,
runtime, runtime,
movieRuntimeFormat, movieRuntimeFormat,
certification certification
@@ -87,8 +85,6 @@ class AddNewMovieSearchResult extends Component {
isNewAddMovieModalOpen isNewAddMovieModalOpen
} = this.state; } = this.state;
const hasMovieFile = !!movieFile;
const linkProps = isExistingMovie ? { to: `/movie/${titleSlug}` } : { onPress: this.onPress }; const linkProps = isExistingMovie ? { to: `/movie/${titleSlug}` } : { onPress: this.onPress };
const posterWidth = 167; const posterWidth = 167;
const posterHeight = 250; const posterHeight = 250;
@@ -124,13 +120,13 @@ class AddNewMovieSearchResult extends Component {
{ {
isExistingMovie && isExistingMovie &&
<MovieIndexProgressBar <MovieIndexProgressBar
movieId={existingMovieId}
movieFile={movieFile}
monitored={monitored} monitored={monitored}
hasFile={hasMovieFile} hasFile={hasFile}
status={status} status={status}
width={posterWidth} width={posterWidth}
detailedProgressBar={true} detailedProgressBar={true}
queueStatus={queueStatus}
queueState={queueState}
isAvailable={isAvailable} isAvailable={isAvailable}
/> />
} }
@@ -201,46 +197,13 @@ class AddNewMovieSearchResult extends Component {
/> />
</Label> </Label>
{
ratings.imdb ?
<Label size={sizes.LARGE}>
<ImdbRating
ratings={ratings}
iconSize={13}
/>
</Label> :
null
}
{ {
!!studio && !!studio &&
<Label size={sizes.LARGE}> <Label size={sizes.LARGE}>
<Icon {studio}
name={icons.STUDIO}
size={13}
/>
<span className={styles.studio}>
{studio}
</span>
</Label> </Label>
} }
{
genres.length > 0 ?
<Label size={sizes.LARGE}>
<Icon
name={icons.GENRE}
size={13}
/>
<span className={styles.genres}>
{genres.slice(0, 3).join(', ')}
</span>
</Label> :
null
}
<Tooltip <Tooltip
anchor={ anchor={
<Label <Label
@@ -252,15 +215,15 @@ class AddNewMovieSearchResult extends Component {
/> />
<span className={styles.links}> <span className={styles.links}>
{translate('Links')} Links
</span> </span>
</Label> </Label>
} }
tooltip={ tooltip={
<MovieDetailsLinks <MovieDetailsLinks
tmdbId={tmdbId} tmdbId={tmdbId}
imdbId={imdbId}
youTubeTrailerId={youTubeTrailerId} youTubeTrailerId={youTubeTrailerId}
imdbId={imdbId}
/> />
} }
canFlip={true} canFlip={true}
@@ -271,10 +234,9 @@ class AddNewMovieSearchResult extends Component {
{ {
isExistingMovie && isSmallScreen && isExistingMovie && isSmallScreen &&
<MovieStatusLabel <MovieStatusLabel
hasMovieFiles={hasMovieFile} hasMovieFiles={hasFile}
monitored={monitored} monitored={monitored}
isAvailable={isAvailable} isAvailable={isAvailable}
queueItem={queueItem}
id={id} id={id}
useLabel={true} useLabel={true}
colorImpairedMode={colorImpairedMode} colorImpairedMode={colorImpairedMode}
@@ -311,29 +273,25 @@ AddNewMovieSearchResult.propTypes = {
titleSlug: PropTypes.string.isRequired, titleSlug: PropTypes.string.isRequired,
year: PropTypes.number.isRequired, year: PropTypes.number.isRequired,
studio: PropTypes.string, studio: PropTypes.string,
genres: PropTypes.arrayOf(PropTypes.string),
status: PropTypes.string.isRequired, status: PropTypes.string.isRequired,
overview: PropTypes.string, overview: PropTypes.string,
ratings: PropTypes.object.isRequired, ratings: PropTypes.object.isRequired,
folder: PropTypes.string.isRequired, folder: PropTypes.string.isRequired,
images: PropTypes.arrayOf(PropTypes.object).isRequired, images: PropTypes.arrayOf(PropTypes.object).isRequired,
existingMovieId: PropTypes.number,
isExistingMovie: PropTypes.bool.isRequired, isExistingMovie: PropTypes.bool.isRequired,
isExclusionMovie: PropTypes.bool.isRequired, isExclusionMovie: PropTypes.bool.isRequired,
isSmallScreen: PropTypes.bool.isRequired, isSmallScreen: PropTypes.bool.isRequired,
id: PropTypes.number, id: PropTypes.number,
queueItems: PropTypes.arrayOf(PropTypes.object),
monitored: PropTypes.bool.isRequired, monitored: PropTypes.bool.isRequired,
hasFile: PropTypes.bool.isRequired,
isAvailable: PropTypes.bool.isRequired, isAvailable: PropTypes.bool.isRequired,
movieFile: PropTypes.object,
queueItem: PropTypes.object,
colorImpairedMode: PropTypes.bool, colorImpairedMode: PropTypes.bool,
queueStatus: PropTypes.string,
queueState: PropTypes.string,
runtime: PropTypes.number.isRequired, runtime: PropTypes.number.isRequired,
movieRuntimeFormat: PropTypes.string.isRequired, movieRuntimeFormat: PropTypes.string.isRequired,
certification: PropTypes.string certification: PropTypes.string
}; };
AddNewMovieSearchResult.defaultProps = {
genres: []
};
export default AddNewMovieSearchResult; export default AddNewMovieSearchResult;

View File

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

View File

@@ -10,7 +10,6 @@ import styles from './ImportMovieRow.css';
function ImportMovieRow(props) { function ImportMovieRow(props) {
const { const {
id, id,
relativePath,
monitor, monitor,
qualityProfileId, qualityProfileId,
minimumAvailability, minimumAvailability,
@@ -32,7 +31,7 @@ function ImportMovieRow(props) {
/> />
<VirtualTableRowCell className={styles.folder}> <VirtualTableRowCell className={styles.folder}>
{relativePath} {id}
</VirtualTableRowCell> </VirtualTableRowCell>
<VirtualTableRowCell className={styles.movie}> <VirtualTableRowCell className={styles.movie}>
@@ -74,7 +73,6 @@ function ImportMovieRow(props) {
ImportMovieRow.propTypes = { ImportMovieRow.propTypes = {
id: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
relativePath: PropTypes.string.isRequired,
monitor: PropTypes.string.isRequired, monitor: PropTypes.string.isRequired,
qualityProfileId: PropTypes.number.isRequired, qualityProfileId: PropTypes.number.isRequired,
minimumAvailability: PropTypes.string.isRequired, minimumAvailability: PropTypes.string.isRequired,

View File

@@ -30,7 +30,7 @@ class ImportMovieTable extends Component {
unmappedFolders.forEach((unmappedFolder) => { unmappedFolders.forEach((unmappedFolder) => {
const id = unmappedFolder.name; const id = unmappedFolder.name;
onMovieLookup(id, unmappedFolder.path, unmappedFolder.relativePath); onMovieLookup(id, unmappedFolder.path);
onSetImportMovieValue({ onSetImportMovieValue({
id, id,

View File

@@ -25,11 +25,10 @@ function createMapStateToProps() {
function createMapDispatchToProps(dispatch, props) { function createMapDispatchToProps(dispatch, props) {
return { return {
onMovieLookup(name, path, relativePath) { onMovieLookup(name, path) {
dispatch(queueLookupMovie({ dispatch(queueLookupMovie({
name, name,
path, path,
relativePath,
term: name term: name
})); }));
}, },

View File

@@ -32,7 +32,7 @@
.contentContainer { .contentContainer {
z-index: $popperZIndex; z-index: $popperZIndex;
margin-top: 4px; margin-top: 4px;
/* 400px container width with 8px padding on each side */ /* 400px container witdh with 8px padding on each side */
width: 384px; width: 384px;
} }

View File

@@ -148,7 +148,7 @@ class ImportMovieSelectFolder extends Component {
className={styles.addErrorAlert} className={styles.addErrorAlert}
kind={kinds.DANGER} kind={kinds.DANGER}
> >
{translate('AddRootFolderError')} {translate('UnableToAddRootFolder')}
<ul> <ul>
{ {

View File

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

View File

@@ -65,12 +65,12 @@ function AppUpdatedModalContent(props) {
return ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
<ModalHeader> <ModalHeader>
{translate('AppUpdated')} {translate('AppUpdated', { appName: 'Radarr' })}
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
<div> <div>
<InlineMarkdown data={translate('AppUpdatedVersion', { version })} blockClassName={styles.version} /> <InlineMarkdown data={translate('AppUpdatedVersion', { appName: 'Radarr', version })} blockClassName={styles.version} />
</div> </div>
{ {

View File

@@ -28,11 +28,11 @@ function ConnectionLostModal(props) {
<ModalBody> <ModalBody>
<div> <div>
{translate('ConnectionLostToBackend')} {translate('ConnectionLostToBackend', { appName: 'Radarr' })}
</div> </div>
<div className={styles.automatic}> <div className={styles.automatic}>
{translate('ConnectionLostReconnect')} {translate('ConnectionLostReconnect', { appName: 'Radarr' })}
</div> </div>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>

View File

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

View File

@@ -1,7 +1,6 @@
import InteractiveImportAppState from 'App/State/InteractiveImportAppState'; import InteractiveImportAppState from 'App/State/InteractiveImportAppState';
import CalendarAppState from './CalendarAppState'; import CalendarAppState from './CalendarAppState';
import CommandAppState from './CommandAppState'; import CommandAppState from './CommandAppState';
import HistoryAppState from './HistoryAppState';
import MovieCollectionAppState from './MovieCollectionAppState'; import MovieCollectionAppState from './MovieCollectionAppState';
import MovieFilesAppState from './MovieFilesAppState'; import MovieFilesAppState from './MovieFilesAppState';
import MoviesAppState, { MovieIndexAppState } from './MoviesAppState'; import MoviesAppState, { MovieIndexAppState } from './MoviesAppState';
@@ -44,19 +43,9 @@ export interface CustomFilter {
filers: PropertyFilter[]; filers: PropertyFilter[];
} }
export interface AppSectionState {
dimensions: {
isSmallScreen: boolean;
width: number;
height: number;
};
}
interface AppState { interface AppState {
app: AppSectionState;
calendar: CalendarAppState; calendar: CalendarAppState;
commands: CommandAppState; commands: CommandAppState;
history: HistoryAppState;
interactiveImport: InteractiveImportAppState; interactiveImport: InteractiveImportAppState;
movieCollections: MovieCollectionAppState; movieCollections: MovieCollectionAppState;
movieFiles: MovieFilesAppState; movieFiles: MovieFilesAppState;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -95,7 +95,7 @@ class AgendaEvent extends Component {
<div className={styles.overlay}> <div className={styles.overlay}>
<div className={styles.date}> <div className={styles.date}>
{showDate ? startTime.format(longDateFormat) : null} {(showDate) ? startTime.format(longDateFormat) : null}
</div> </div>
<div className={styles.releaseIcon}> <div className={styles.releaseIcon}>
@@ -147,7 +147,7 @@ class AgendaEvent extends Component {
className={styles.statusIcon} className={styles.statusIcon}
name={icons.MOVIE_FILE} name={icons.MOVIE_FILE}
kind={kinds.WARNING} kind={kinds.WARNING}
title={translate('QualityCutoffNotMet')} title={translate('QualityCutoffHasNotBeenMet')}
/> />
} }
</div> </div>

View File

@@ -33,7 +33,9 @@ class Calendar extends Component {
{ {
!isFetching && !!error && !isFetching && !!error &&
<Alert kind={kinds.DANGER}>{translate('CalendarLoadError')}</Alert> <Alert kind={kinds.DANGER}>
{translate('UnableToLoadTheCalendar')}
</Alert>
} }
{ {

View File

@@ -55,7 +55,7 @@ class CalendarConnector extends Component {
gotoCalendarToday gotoCalendarToday
} = this.props; } = this.props;
registerPagePopulator(this.repopulate, ['movieFileUpdated', 'movieFileDeleted']); registerPagePopulator(this.repopulate);
if (useCurrentPage) { if (useCurrentPage) {
fetchCalendar(); fetchCalendar();

View File

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

View File

@@ -104,7 +104,7 @@ class CalendarPage extends Component {
<PageToolbar> <PageToolbar>
<PageToolbarSection> <PageToolbarSection>
<PageToolbarButton <PageToolbarButton
label={translate('ICalLink')} label={translate('iCalLink')}
iconName={icons.CALENDAR} iconName={icons.CALENDAR}
onPress={this.onGetCalendarLinkPress} onPress={this.onGetCalendarLinkPress}
/> />
@@ -112,7 +112,7 @@ class CalendarPage extends Component {
<PageToolbarSeparator /> <PageToolbarSeparator />
<PageToolbarButton <PageToolbarButton
label={translate('RssSync')} label={translate('RSSSync')}
iconName={icons.RSS} iconName={icons.RSS}
isSpinning={isRssSyncExecuting} isSpinning={isRssSyncExecuting}
onPress={onRssSyncPress} onPress={onRssSyncPress}
@@ -180,7 +180,7 @@ class CalendarPage extends Component {
{ {
!movieError && movieIsPopulated && !hasMovie && !movieError && movieIsPopulated && !hasMovie &&
<NoMovie totalItems={0} /> <NoMovie />
} }
{ {

View File

@@ -48,10 +48,6 @@ $fullColorGradient: rgba(244, 245, 246, 0.2);
.statusContainer { .statusContainer {
display: flex; display: flex;
align-items: center; align-items: center;
&:global(.fullColor) {
filter: var(--calendarFullColorFilter)
}
} }
.statusIcon { .statusIcon {

View File

@@ -76,18 +76,12 @@ class CalendarEvent extends Component {
{title} {title}
</div> </div>
<div <div className={styles.statusContainer}>
className={classNames(
styles.statusContainer,
fullColorEvents && 'fullColor'
)}
>
{ {
queueItem ? queueItem ?
<span className={styles.statusIcon}> <span className={styles.statusIcon}>
<CalendarEventQueueDetails <CalendarEventQueueDetails
{...queueItem} {...queueItem}
fullColorEvents={fullColorEvents}
/> />
</span> : </span> :
null null
@@ -104,14 +98,12 @@ class CalendarEvent extends Component {
} }
{ {
showCutoffUnmetIcon && showCutoffUnmetIcon && !!movieFile && movieFile.qualityCutoffNotMet ?
!!movieFile &&
movieFile.qualityCutoffNotMet ?
<Icon <Icon
className={styles.statusIcon} className={styles.statusIcon}
name={icons.MOVIE_FILE} name={icons.MOVIE_FILE}
kind={kinds.WARNING} kind={kinds.WARNING}
title={translate('QualityCutoffNotMet')} title={translate('QualityCutoffHasNotBeenMet')}
/> : /> :
null null
} }

View File

@@ -126,7 +126,7 @@ class CalendarHeader extends Component {
isDisabled={view === calendarViews.AGENDA} isDisabled={view === calendarViews.AGENDA}
onPress={onTodayPress} onPress={onTodayPress}
> >
{translate('Today')} Today
</Button> </Button>
</div> </div>

View File

@@ -20,11 +20,10 @@ function Legend(props) {
if (showCutoffUnmetIcon) { if (showCutoffUnmetIcon) {
iconsToShow.push( iconsToShow.push(
<LegendIconItem <LegendIconItem
name={translate('CutoffNotMet')} name={translate('CutoffUnmet')}
icon={icons.MOVIE_FILE} icon={icons.MOVIE_FILE}
kind={kinds.WARNING} kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
fullColorEvents={fullColorEvents} tooltip={translate('QualityOrLangCutoffHasNotBeenMet')}
tooltip={translate('QualityCutoffNotMet')}
/> />
); );
} }

View File

@@ -4,8 +4,4 @@
.icon { .icon {
margin-right: 5px; margin-right: 5px;
&:global(.fullColorEvents) {
filter: var(--calendarFullColorFilter)
}
} }

View File

@@ -1,4 +1,3 @@
import classNames from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
@@ -7,9 +6,9 @@ import styles from './LegendIconItem.css';
function LegendIconItem(props) { function LegendIconItem(props) {
const { const {
name, name,
fullColorEvents,
icon, icon,
kind, kind,
darken,
tooltip tooltip
} = props; } = props;
@@ -19,11 +18,9 @@ function LegendIconItem(props) {
title={tooltip} title={tooltip}
> >
<Icon <Icon
className={classNames( className={styles.icon}
styles.icon,
fullColorEvents && 'fullColorEvents'
)}
name={icon} name={icon}
darken={darken}
kind={kind} kind={kind}
/> />
@@ -34,10 +31,14 @@ function LegendIconItem(props) {
LegendIconItem.propTypes = { LegendIconItem.propTypes = {
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
fullColorEvents: PropTypes.bool.isRequired,
icon: PropTypes.object.isRequired, icon: PropTypes.object.isRequired,
kind: PropTypes.string.isRequired, kind: PropTypes.string.isRequired,
darken: PropTypes.bool.isRequired,
tooltip: PropTypes.string.isRequired tooltip: PropTypes.string.isRequired
}; };
LegendIconItem.defaultProps = {
darken: false
};
export default LegendIconItem; export default LegendIconItem;

View File

@@ -135,7 +135,7 @@ class CalendarOptionsModalContent extends Component {
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="showCutoffUnmetIcon" name="showCutoffUnmetIcon"
value={showCutoffUnmetIcon} value={showCutoffUnmetIcon}
helpText={translate('IconForCutoffUnmetHelpText')} helpText={translate('ShowCutoffUnmetIconHelpText')}
onChange={this.onOptionInputChange} onChange={this.onOptionInputChange}
/> />
</FormGroup> </FormGroup>
@@ -177,7 +177,7 @@ class CalendarOptionsModalContent extends Component {
values={weekColumnOptions} values={weekColumnOptions}
value={calendarWeekColumnHeader} value={calendarWeekColumnHeader}
onChange={this.onGlobalInputChange} onChange={this.onGlobalInputChange}
helpText={translate('WeekColumnHeaderHelpText')} helpText={translate('SettingsWeekColumnHeaderHelpText')}
/> />
</FormGroup> </FormGroup>

View File

@@ -109,7 +109,7 @@ class CalendarLinkModalContent extends Component {
return ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
<ModalHeader> <ModalHeader>
{translate('CalendarFeed')} {translate('RadarrCalendarFeed')}
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
@@ -121,19 +121,19 @@ class CalendarLinkModalContent extends Component {
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="unmonitored" name="unmonitored"
value={unmonitored} value={unmonitored}
helpText={translate('ICalIncludeUnmonitoredMoviesHelpText')} helpText={translate('UnmonitoredHelpText')}
onChange={this.onInputChange} onChange={this.onInputChange}
/> />
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<FormLabel>{translate('ICalShowAsAllDayEvents')}</FormLabel> <FormLabel>{translate('ShowAsAllDayEvents')}</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="asAllDay" name="asAllDay"
value={asAllDay} value={asAllDay}
helpText={translate('ICalShowAsAllDayEventsHelpText')} helpText={translate('AsAllDayHelpText')}
onChange={this.onInputChange} onChange={this.onInputChange}
/> />
</FormGroup> </FormGroup>
@@ -145,7 +145,7 @@ class CalendarLinkModalContent extends Component {
type={inputTypes.TAG} type={inputTypes.TAG}
name="tags" name="tags"
value={tags} value={tags}
helpText={translate('ICalTagsMoviesHelpText')} helpText={translate('TagsHelpText')}
onChange={this.onInputChange} onChange={this.onInputChange}
/> />
</FormGroup> </FormGroup>
@@ -160,7 +160,7 @@ class CalendarLinkModalContent extends Component {
name="iCalHttpUrl" name="iCalHttpUrl"
value={iCalHttpUrl} value={iCalHttpUrl}
readOnly={true} readOnly={true}
helpText={translate('ICalFeedHelpText')} helpText={translate('ICalHttpUrlHelpText')}
buttons={[ buttons={[
<ClipboardButton <ClipboardButton
key="copy" key="copy"

View File

@@ -6,7 +6,6 @@ import * as commandNames from 'Commands/commandNames';
import withScrollPosition from 'Components/withScrollPosition'; import withScrollPosition from 'Components/withScrollPosition';
import { executeCommand } from 'Store/Actions/commandActions'; import { executeCommand } from 'Store/Actions/commandActions';
import { saveMovieCollections, setMovieCollectionsFilter, setMovieCollectionsSort } from 'Store/Actions/movieCollectionActions'; import { saveMovieCollections, setMovieCollectionsFilter, setMovieCollectionsSort } from 'Store/Actions/movieCollectionActions';
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
import { fetchRootFolders } from 'Store/Actions/rootFolderActions'; import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
import scrollPositions from 'Store/scrollPositions'; import scrollPositions from 'Store/scrollPositions';
import createCollectionClientSideCollectionItemsSelector from 'Store/Selectors/createCollectionClientSideCollectionItemsSelector'; import createCollectionClientSideCollectionItemsSelector from 'Store/Selectors/createCollectionClientSideCollectionItemsSelector';
@@ -39,12 +38,6 @@ function createMapDispatchToProps(dispatch, props) {
dispatchFetchRootFolders() { dispatchFetchRootFolders() {
dispatch(fetchRootFolders()); dispatch(fetchRootFolders());
}, },
dispatchFetchQueueDetails() {
dispatch(fetchQueueDetails());
},
dispatchClearQueueDetails() {
dispatch(clearQueueDetails());
},
onUpdateSelectedPress(payload) { onUpdateSelectedPress(payload) {
dispatch(saveMovieCollections(payload)); dispatch(saveMovieCollections(payload));
}, },
@@ -70,12 +63,10 @@ class CollectionConnector extends Component {
componentDidMount() { componentDidMount() {
registerPagePopulator(this.repopulate); registerPagePopulator(this.repopulate);
this.props.dispatchFetchRootFolders(); this.props.dispatchFetchRootFolders();
this.props.dispatchFetchQueueDetails();
} }
componentWillUnmount() { componentWillUnmount() {
unregisterPagePopulator(this.repopulate); unregisterPagePopulator(this.repopulate);
this.props.dispatchClearQueueDetails();
} }
// //
@@ -108,9 +99,7 @@ CollectionConnector.propTypes = {
isSmallScreen: PropTypes.bool.isRequired, isSmallScreen: PropTypes.bool.isRequired,
view: PropTypes.string.isRequired, view: PropTypes.string.isRequired,
onUpdateSelectedPress: PropTypes.func.isRequired, onUpdateSelectedPress: PropTypes.func.isRequired,
dispatchFetchRootFolders: PropTypes.func.isRequired, dispatchFetchRootFolders: PropTypes.func.isRequired
dispatchFetchQueueDetails: PropTypes.func.isRequired,
dispatchClearQueueDetails: PropTypes.func.isRequired
}; };
export default withScrollPosition( export default withScrollPosition(

View File

@@ -14,50 +14,6 @@ import styles from './CollectionFooter.css';
const NO_CHANGE = 'noChange'; const NO_CHANGE = 'noChange';
const monitoredOptions = [
{
key: NO_CHANGE,
get value() {
return translate('NoChange');
},
isDisabled: 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');
},
isDisabled: true
},
{
key: 'yes',
get value() {
return translate('Yes');
}
},
{
key: 'no',
get value() {
return translate('No');
}
}
];
class CollectionFooter extends Component { class CollectionFooter extends Component {
// //
@@ -67,12 +23,12 @@ class CollectionFooter extends Component {
super(props, context); super(props, context);
this.state = { this.state = {
monitored: NO_CHANGE,
monitor: NO_CHANGE, monitor: NO_CHANGE,
monitored: NO_CHANGE,
qualityProfileId: NO_CHANGE, qualityProfileId: NO_CHANGE,
minimumAvailability: NO_CHANGE, minimumAvailability: NO_CHANGE,
rootFolderPath: NO_CHANGE, rootFolderPath: NO_CHANGE,
searchOnAdd: NO_CHANGE destinationRootFolder: null
}; };
} }
@@ -88,9 +44,8 @@ class CollectionFooter extends Component {
monitored: NO_CHANGE, monitored: NO_CHANGE,
monitor: NO_CHANGE, monitor: NO_CHANGE,
qualityProfileId: NO_CHANGE, qualityProfileId: NO_CHANGE,
minimumAvailability: NO_CHANGE,
rootFolderPath: NO_CHANGE, rootFolderPath: NO_CHANGE,
searchOnAdd: NO_CHANGE minimumAvailability: NO_CHANGE
}); });
} }
@@ -108,12 +63,11 @@ class CollectionFooter extends Component {
onUpdateSelectedPress = () => { onUpdateSelectedPress = () => {
const { const {
monitored,
monitor, monitor,
monitored,
qualityProfileId, qualityProfileId,
minimumAvailability, minimumAvailability,
rootFolderPath, rootFolderPath
searchOnAdd
} = this.state; } = this.state;
const changes = {}; const changes = {};
@@ -138,10 +92,6 @@ class CollectionFooter extends Component {
changes.rootFolderPath = rootFolderPath; changes.rootFolderPath = rootFolderPath;
} }
if (searchOnAdd !== NO_CHANGE) {
changes.searchOnAdd = searchOnAdd === 'yes';
}
this.props.onUpdateSelectedPress(changes); this.props.onUpdateSelectedPress(changes);
}; };
@@ -159,10 +109,15 @@ class CollectionFooter extends Component {
monitor, monitor,
qualityProfileId, qualityProfileId,
minimumAvailability, minimumAvailability,
rootFolderPath, rootFolderPath
searchOnAdd
} = this.state; } = 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; const selectedCount = selectedIds.length;
return ( return (
@@ -170,7 +125,7 @@ class CollectionFooter extends Component {
<div className={styles.inputContainer}> <div className={styles.inputContainer}>
<CollectionFooterLabel <CollectionFooterLabel
label={translate('MonitorCollection')} label={translate('MonitorCollection')}
isSaving={isSaving && monitored !== NO_CHANGE} isSaving={isSaving}
/> />
<SelectInput <SelectInput
@@ -185,7 +140,7 @@ class CollectionFooter extends Component {
<div className={styles.inputContainer}> <div className={styles.inputContainer}>
<CollectionFooterLabel <CollectionFooterLabel
label={translate('MonitorMovies')} label={translate('MonitorMovies')}
isSaving={isSaving && monitor !== NO_CHANGE} isSaving={isSaving}
/> />
<SelectInput <SelectInput
@@ -243,25 +198,10 @@ class CollectionFooter extends Component {
/> />
</div> </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.buttonContainer}>
<div className={styles.buttonContainerContent}> <div className={styles.buttonContainerContent}>
<CollectionFooterLabel <CollectionFooterLabel
label={translate('CountCollectionsSelected', { count: selectedCount })} label={translate('CollectionsSelectedInterp', [selectedCount])}
isSaving={false} isSaving={false}
/> />

View File

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

View File

@@ -61,7 +61,6 @@ class CollectionMovie extends Component {
const { const {
id, id,
title, title,
status,
overview, overview,
year, year,
tmdbId, tmdbId,
@@ -70,7 +69,6 @@ class CollectionMovie extends Component {
hasFile, hasFile,
folder, folder,
isAvailable, isAvailable,
movieFile,
isExistingMovie, isExistingMovie,
posterWidth, posterWidth,
posterHeight, posterHeight,
@@ -125,15 +123,13 @@ class CollectionMovie extends Component {
<div className={styles.overlay}> <div className={styles.overlay}>
<div className={styles.overlayTitle}> <div className={styles.overlayTitle}>
{title} {year > 0 ? `(${year})` : ''} {title}
</div> </div>
{ {
id ? id &&
<div className={styles.overlayStatus}> <div className={styles.overlayStatus}>
<MovieIndexProgressBar <MovieIndexProgressBar
movieId={id}
movieFile={movieFile}
monitored={monitored} monitored={monitored}
hasFile={hasFile} hasFile={hasFile}
status={status} status={status}
@@ -142,8 +138,7 @@ class CollectionMovie extends Component {
detailedProgressBar={detailedProgressBar} detailedProgressBar={detailedProgressBar}
isAvailable={isAvailable} isAvailable={isAvailable}
/> />
</div> : </div>
null
} }
</div> </div>
</Link> </Link>
@@ -176,14 +171,12 @@ CollectionMovie.propTypes = {
id: PropTypes.number, id: PropTypes.number,
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
year: PropTypes.number.isRequired, year: PropTypes.number.isRequired,
status: PropTypes.string.isRequired,
overview: PropTypes.string.isRequired, overview: PropTypes.string.isRequired,
monitored: PropTypes.bool, monitored: PropTypes.bool,
collectionId: PropTypes.number.isRequired, collectionId: PropTypes.number.isRequired,
hasFile: PropTypes.bool, hasFile: PropTypes.bool,
folder: PropTypes.string, folder: PropTypes.string,
isAvailable: PropTypes.bool, isAvailable: PropTypes.bool,
movieFile: PropTypes.object,
images: PropTypes.arrayOf(PropTypes.object).isRequired, images: PropTypes.arrayOf(PropTypes.object).isRequired,
posterWidth: PropTypes.number.isRequired, posterWidth: PropTypes.number.isRequired,
posterHeight: PropTypes.number.isRequired, posterHeight: PropTypes.number.isRequired,

View File

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

View File

@@ -14,7 +14,6 @@ class CollectionMovieLabel extends Component {
const { const {
id, id,
title, title,
year,
status, status,
monitored, monitored,
isAvailable, isAvailable,
@@ -36,7 +35,9 @@ class CollectionMovieLabel extends Component {
} }
<span> <span>
{title} {year > 0 ? `(${year})` : ''} {
title
}
</span> </span>
</div> </div>
@@ -61,7 +62,6 @@ class CollectionMovieLabel extends Component {
CollectionMovieLabel.propTypes = { CollectionMovieLabel.propTypes = {
id: PropTypes.number, id: PropTypes.number,
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
year: PropTypes.number.isRequired,
status: PropTypes.string, status: PropTypes.string,
isAvailable: PropTypes.bool, isAvailable: PropTypes.bool,
monitored: PropTypes.bool, monitored: PropTypes.bool,
@@ -74,7 +74,11 @@ CollectionMovieLabel.propTypes = {
CollectionMovieLabel.defaultProps = { CollectionMovieLabel.defaultProps = {
isSaving: false, isSaving: false,
statistics: {} statistics: {
episodeFileCount: 0,
totalEpisodeCount: 0,
percentOfEpisodes: 0
}
}; };
export default CollectionMovieLabel; export default CollectionMovieLabel;

View File

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

View File

@@ -13,7 +13,6 @@ export interface CommandBody {
trigger: string; trigger: string;
suppressMessages: boolean; suppressMessages: boolean;
movieId?: number; movieId?: number;
movieIds?: number[];
} }
interface Command extends ModelBase { interface Command extends ModelBase {

View File

@@ -1,7 +1,9 @@
.description {
line-height: $lineHeight;
}
.description { .description {
margin-left: 0; margin-left: 0;
line-height: $lineHeight;
overflow-wrap: break-word;
} }
@media (min-width: 768px) { @media (min-width: 768px) {

View File

@@ -1,4 +1,3 @@
import { maxBy } from 'lodash';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import FormInputGroup from 'Components/Form/FormInputGroup'; import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -51,7 +50,7 @@ class FilterBuilderModalContent extends Component {
if (id) { if (id) {
dispatchSetFilter({ selectedFilterKey: id }); dispatchSetFilter({ selectedFilterKey: id });
} else { } else {
const last = maxBy(customFilters, 'id'); const last = customFilters[customFilters.length -1];
dispatchSetFilter({ selectedFilterKey: last.id }); dispatchSetFilter({ selectedFilterKey: last.id });
} }
@@ -109,7 +108,7 @@ class FilterBuilderModalContent extends Component {
this.setState({ this.setState({
labelErrors: [ labelErrors: [
{ {
message: translate('LabelIsRequired') message: 'Label is required'
} }
] ]
}); });
@@ -147,13 +146,13 @@ class FilterBuilderModalContent extends Component {
return ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
<ModalHeader> <ModalHeader>
{translate('CustomFilter')} Custom Filter
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
<div className={styles.labelContainer}> <div className={styles.labelContainer}>
<div className={styles.label}> <div className={styles.label}>
{translate('Label')} Label
</div> </div>
<div className={styles.labelInputContainer}> <div className={styles.labelInputContainer}>
@@ -167,7 +166,9 @@ class FilterBuilderModalContent extends Component {
</div> </div>
</div> </div>
<div className={styles.label}>{translate('Filters')}</div> <div className={styles.label}>
{translate('Filters')}
</div>
<div className={styles.rows}> <div className={styles.rows}>
{ {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -37,8 +37,8 @@ class CustomFilter extends Component {
dispatchSetFilter dispatchSetFilter
} = this.props; } = this.props;
// Assume that delete and then unmounting means the deletion was successful. // Assume that delete and then unmounting means the delete was successful.
// Moving this check to an ancestor would be more accurate, but would have // Moving this check to a ancestor would be more accurate, but would have
// more boilerplate. // more boilerplate.
if (this.state.isDeleting && id === selectedFilterKey) { if (this.state.isDeleting && id === selectedFilterKey) {
dispatchSetFilter({ selectedFilterKey: 'all' }); dispatchSetFilter({ selectedFilterKey: 'all' });

View File

@@ -30,24 +30,22 @@ function CustomFiltersModalContent(props) {
<ModalBody> <ModalBody>
{ {
customFilters customFilters.map((customFilter) => {
.sort((a, b) => a.label.localeCompare(b.label)) return (
.map((customFilter) => { <CustomFilter
return ( key={customFilter.id}
<CustomFilter id={customFilter.id}
key={customFilter.id} label={customFilter.label}
id={customFilter.id} filters={customFilter.filters}
label={customFilter.label} selectedFilterKey={selectedFilterKey}
filters={customFilter.filters} isDeleting={isDeleting}
selectedFilterKey={selectedFilterKey} deleteError={deleteError}
isDeleting={isDeleting} dispatchSetFilter={dispatchSetFilter}
deleteError={deleteError} dispatchDeleteCustomFilter={dispatchDeleteCustomFilter}
dispatchSetFilter={dispatchSetFilter} onEditPress={onEditCustomFilter}
dispatchDeleteCustomFilter={dispatchDeleteCustomFilter} />
onEditPress={onEditCustomFilter} );
/> })
);
})
} }
<div className={styles.addButtonContainer}> <div className={styles.addButtonContainer}>

View File

@@ -36,7 +36,7 @@ function AvailabilitySelectInput(props) {
values.unshift({ values.unshift({
key: 'noChange', key: 'noChange',
value: translate('NoChange'), value: translate('NoChange'),
isDisabled: true disabled: true
}); });
} }
@@ -44,7 +44,7 @@ function AvailabilitySelectInput(props) {
values.unshift({ values.unshift({
key: 'mixed', key: 'mixed',
value: '(Mixed)', value: '(Mixed)',
isDisabled: true disabled: true
}); });
} }

View File

@@ -25,8 +25,7 @@ function createMapStateToProps() {
const values = _.map(filteredItems.sort(sortByName), (downloadClient) => { const values = _.map(filteredItems.sort(sortByName), (downloadClient) => {
return { return {
key: downloadClient.id, key: downloadClient.id,
value: downloadClient.name, value: downloadClient.name
hint: `(${downloadClient.id})`
}; };
}); });

View File

@@ -19,7 +19,7 @@
.isDisabled { .isDisabled {
opacity: 0.7; opacity: 0.7;
cursor: not-allowed !important; cursor: not-allowed;
} }
.dropdownArrowContainer { .dropdownArrowContainer {

View File

@@ -17,7 +17,6 @@ import IndexerSelectInputConnector from './IndexerSelectInputConnector';
import KeyValueListInput from './KeyValueListInput'; import KeyValueListInput from './KeyValueListInput';
import LanguageSelectInputConnector from './LanguageSelectInputConnector'; import LanguageSelectInputConnector from './LanguageSelectInputConnector';
import MovieMonitoredSelectInput from './MovieMonitoredSelectInput'; import MovieMonitoredSelectInput from './MovieMonitoredSelectInput';
import MovieTagInput from './MovieTagInput';
import NumberInput from './NumberInput'; import NumberInput from './NumberInput';
import OAuthInputConnector from './OAuthInputConnector'; import OAuthInputConnector from './OAuthInputConnector';
import PasswordInput from './PasswordInput'; import PasswordInput from './PasswordInput';
@@ -90,10 +89,6 @@ function getComponent(type) {
case inputTypes.DYNAMIC_SELECT: case inputTypes.DYNAMIC_SELECT:
return EnhancedSelectInputConnector; return EnhancedSelectInputConnector;
case inputTypes.MOVIE_TAG:
return MovieTagInput;
case inputTypes.TAG: case inputTypes.TAG:
return TagInputConnector; return TagInputConnector;
@@ -272,7 +267,6 @@ FormInputGroup.propTypes = {
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
value: PropTypes.any, value: PropTypes.any,
values: PropTypes.arrayOf(PropTypes.any), values: PropTypes.arrayOf(PropTypes.any),
isDisabled: PropTypes.bool,
type: PropTypes.string.isRequired, type: PropTypes.string.isRequired,
kind: PropTypes.oneOf(kinds.all), kind: PropTypes.oneOf(kinds.all),
min: PropTypes.number, min: PropTypes.number,
@@ -287,7 +281,6 @@ FormInputGroup.propTypes = {
includeNoChange: PropTypes.bool, includeNoChange: PropTypes.bool,
includeNoChangeDisabled: PropTypes.bool, includeNoChangeDisabled: PropTypes.bool,
selectedValueOptions: PropTypes.object, selectedValueOptions: PropTypes.object,
indexerFlags: PropTypes.number,
pending: PropTypes.bool, pending: PropTypes.bool,
errors: PropTypes.arrayOf(PropTypes.object), errors: PropTypes.arrayOf(PropTypes.object),
warnings: PropTypes.arrayOf(PropTypes.object), warnings: PropTypes.arrayOf(PropTypes.object),

View File

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

View File

@@ -4,18 +4,22 @@ import { createSelector } from 'reselect';
import AppState from 'App/State/AppState'; import AppState from 'App/State/AppState';
import EnhancedSelectInput from './EnhancedSelectInput'; import EnhancedSelectInput from './EnhancedSelectInput';
interface IndexerFlagsSelectInputProps {
name: string;
indexerFlags: number;
onChange(payload: object): void;
}
const selectIndexerFlagsValues = (selectedFlags: number) => const selectIndexerFlagsValues = (selectedFlags: number) =>
createSelector( createSelector(
(state: AppState) => state.settings.indexerFlags, (state: AppState) => state.settings.indexerFlags,
(indexerFlags) => { (indexerFlags) => {
const value = indexerFlags.items.reduce((acc: number[], { id }) => { const value = indexerFlags.items
// eslint-disable-next-line no-bitwise .filter(
if ((selectedFlags & id) === id) { // eslint-disable-next-line no-bitwise
acc.push(id); (item) => (selectedFlags & item.id) === item.id
} )
.map(({ id }) => id);
return acc;
}, []);
const values = indexerFlags.items.map(({ id, name }) => ({ const values = indexerFlags.items.map(({ id, name }) => ({
key: id, key: id,
@@ -29,12 +33,6 @@ const selectIndexerFlagsValues = (selectedFlags: number) =>
} }
); );
interface IndexerFlagsSelectInputProps {
name: string;
indexerFlags: number;
onChange(payload: object): void;
}
function IndexerFlagsSelectInput(props: IndexerFlagsSelectInputProps) { function IndexerFlagsSelectInput(props: IndexerFlagsSelectInputProps) {
const { indexerFlags, onChange } = props; const { indexerFlags, onChange } = props;

View File

@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import monitorOptions from 'Utilities/Movie/monitorOptions'; import monitorOptions from 'Utilities/Movie/monitorOptions';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import EnhancedSelectInput from './EnhancedSelectInput'; import SelectInput from './SelectInput';
function MovieMonitoredSelectInput(props) { function MovieMonitoredSelectInput(props) {
const values = [...monitorOptions]; const values = [...monitorOptions];
@@ -16,7 +16,7 @@ function MovieMonitoredSelectInput(props) {
values.unshift({ values.unshift({
key: 'noChange', key: 'noChange',
value: translate('NoChange'), value: translate('NoChange'),
isDisabled: true disabled: true
}); });
} }
@@ -24,12 +24,12 @@ function MovieMonitoredSelectInput(props) {
values.unshift({ values.unshift({
key: 'mixed', key: 'mixed',
value: '(Mixed)', value: '(Mixed)',
isDisabled: true disabled: true
}); });
} }
return ( return (
<EnhancedSelectInput <SelectInput
{...props} {...props}
values={values} values={values}
/> />

View File

@@ -1,53 +0,0 @@
import React, { useCallback } from 'react';
import TagInputConnector from './TagInputConnector';
interface MovieTagInputProps {
name: string;
value: number | number[];
onChange: ({
name,
value,
}: {
name: string;
value: number | number[];
}) => void;
}
export default function MovieTagInput(props: MovieTagInputProps) {
const { value, onChange, ...otherProps } = props;
const isArray = Array.isArray(value);
const handleChange = useCallback(
({ name, value: newValue }: { name: string; value: number[] }) => {
if (isArray) {
onChange({ name, value: newValue });
} else {
onChange({
name,
value: newValue.length ? newValue[newValue.length - 1] : 0,
});
}
},
[isArray, onChange]
);
let finalValue: number[] = [];
if (isArray) {
finalValue = value;
} else if (value === 0) {
finalValue = [];
} else {
finalValue = [value];
}
return (
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore 2786 'TagInputConnector' isn't typed yet
<TagInputConnector
{...otherProps}
value={finalValue}
onChange={handleChange}
/>
);
}

View File

@@ -27,8 +27,6 @@ function getType({ type, selectOptionsProviderAction }) {
return inputTypes.DYNAMIC_SELECT; return inputTypes.DYNAMIC_SELECT;
} }
return inputTypes.SELECT; return inputTypes.SELECT;
case 'movieTag':
return inputTypes.MOVIE_TAG;
case 'tag': case 'tag':
return inputTypes.TEXT_TAG; return inputTypes.TEXT_TAG;
case 'tagSelect': case 'tagSelect':
@@ -39,8 +37,6 @@ function getType({ type, selectOptionsProviderAction }) {
return inputTypes.OAUTH; return inputTypes.OAUTH;
case 'rootFolder': case 'rootFolder':
return inputTypes.ROOT_FOLDER_SELECT; return inputTypes.ROOT_FOLDER_SELECT;
case 'qualityProfile':
return inputTypes.QUALITY_PROFILE_SELECT;
default: default:
return inputTypes.TEXT; return inputTypes.TEXT;
} }

View File

@@ -26,7 +26,7 @@ function createMapStateToProps() {
values.unshift({ values.unshift({
key: 'noChange', key: 'noChange',
value: translate('NoChange'), value: translate('NoChange'),
isDisabled: includeNoChangeDisabled disabled: includeNoChangeDisabled
}); });
} }
@@ -34,7 +34,7 @@ function createMapStateToProps() {
values.unshift({ values.unshift({
key: 'mixed', key: 'mixed',
value: '(Mixed)', value: '(Mixed)',
isDisabled: true disabled: true
}); });
} }

View File

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

View File

@@ -91,7 +91,6 @@ class TextTagInputConnector extends Component {
render() { render() {
return ( return (
<TagInput <TagInput
delimiters={['Tab', 'Enter', ',']}
tagList={[]} tagList={[]}
onTagAdd={this.onTagAdd} onTagAdd={this.onTagAdd}
onTagDelete={this.onTagDelete} onTagDelete={this.onTagDelete}

View File

@@ -12,10 +12,18 @@
.info { .info {
color: var(--infoColor); color: var(--infoColor);
&:global(.darken) {
color: color(var(--infoColor) shade(30%));
}
} }
.pink { .pink {
color: var(--pink); color: var(--pink);
&:global(.darken) {
color: color(var(--pink) shade(30%));
}
} }
.success { .success {

View File

@@ -18,6 +18,7 @@ class Icon extends PureComponent {
kind, kind,
size, size,
title, title,
darken,
isSpinning, isSpinning,
...otherProps ...otherProps
} = this.props; } = this.props;
@@ -26,7 +27,8 @@ class Icon extends PureComponent {
<FontAwesomeIcon <FontAwesomeIcon
className={classNames( className={classNames(
className, className,
styles[kind] styles[kind],
darken && 'darken'
)} )}
icon={name} icon={name}
spin={isSpinning} spin={isSpinning}
@@ -59,6 +61,7 @@ Icon.propTypes = {
kind: PropTypes.string.isRequired, kind: PropTypes.string.isRequired,
size: PropTypes.number.isRequired, size: PropTypes.number.isRequired,
title: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), title: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
darken: PropTypes.bool.isRequired,
isSpinning: PropTypes.bool.isRequired, isSpinning: PropTypes.bool.isRequired,
fixedWidth: PropTypes.bool.isRequired fixedWidth: PropTypes.bool.isRequired
}; };
@@ -66,6 +69,7 @@ Icon.propTypes = {
Icon.defaultProps = { Icon.defaultProps = {
kind: kinds.DEFAULT, kind: kinds.DEFAULT,
size: 14, size: 14,
darken: false,
isSpinning: false, isSpinning: false,
fixedWidth: false fixedWidth: false
}; };

View File

@@ -19,7 +19,7 @@ function ImportListList({ lists, importListList }) {
return ( return (
<Label <Label
key={list.id} key={list.id}
kind={kinds.SUCCESS} kind={kinds.INFO}
size={sizes.MEDIUM} size={sizes.MEDIUM}
> >
{list.name} {list.name}

View File

@@ -40,26 +40,18 @@ class FilterMenuContent extends Component {
} }
{ {
customFilters.length > 0 ? customFilters.map((filter) => {
<MenuItemSeparator /> : return (
null <FilterMenuItem
} key={filter.id}
filterKey={filter.id}
{ selectedFilterKey={selectedFilterKey}
customFilters onPress={onFilterSelect}
.sort((a, b) => a.label.localeCompare(b.label)) >
.map((filter) => { {filter.label}
return ( </FilterMenuItem>
<FilterMenuItem );
key={filter.id} })
filterKey={filter.id}
selectedFilterKey={selectedFilterKey}
onPress={onFilterSelect}
>
{filter.label}
</FilterMenuItem>
);
})
} }
{ {

View File

@@ -63,12 +63,6 @@
width: 1280px; width: 1280px;
} }
.extraExtraLarge {
composes: modal;
width: 1600px;
}
@media only screen and (max-width: $breakpointExtraLarge) { @media only screen and (max-width: $breakpointExtraLarge) {
.modal.extraLarge { .modal.extraLarge {
width: 90%; width: 90%;
@@ -96,8 +90,7 @@
.modal.small, .modal.small,
.modal.medium, .modal.medium,
.modal.large, .modal.large,
.modal.extraLarge, .modal.extraLarge {
.modal.extraExtraLarge {
max-height: 100%; max-height: 100%;
width: 100%; width: 100%;
height: 100% !important; height: 100% !important;

View File

@@ -1,7 +1,6 @@
// This file is automatically generated. // This file is automatically generated.
// Please do not change this file! // Please do not change this file!
interface CssExports { interface CssExports {
'extraExtraLarge': string;
'extraLarge': string; 'extraLarge': string;
'large': string; 'large': string;
'medium': string; 'medium': string;

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