mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-06 13:31:28 -05:00
Compare commits
3 Commits
v5.9.0.905
...
collection
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04b15f2178 | ||
|
|
39d7320a75 | ||
|
|
207a4b19dc |
@@ -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": {}
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
12
.github/dependabot.yml
vendored
12
.github/dependabot.yml
vendored
@@ -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
1
.gitignore
vendored
@@ -126,7 +126,6 @@ coverage*.xml
|
||||
coverage*.json
|
||||
setup/Output/
|
||||
*.~is
|
||||
.mono
|
||||
|
||||
# VS outout folders
|
||||
bin
|
||||
|
||||
7
.vscode/extensions.json
vendored
7
.vscode/extensions.json
vendored
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"esbenp.prettier-vscode",
|
||||
"ms-dotnettools.csdevkit",
|
||||
"ms-vscode-remote.remote-containers"
|
||||
]
|
||||
}
|
||||
26
.vscode/launch.json
vendored
26
.vscode/launch.json
vendored
@@ -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
44
.vscode/tasks.json
vendored
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[](https://dev.azure.com/Radarr/Radarr/_build/latest?definitionId=1&branchName=develop)
|
||||
[](https://translate.servarr.com/engage/radarr/?utm_source=widget)
|
||||
[](https://wiki.servarr.com/radarr/installation/docker)
|
||||
[](https://wiki.servarr.com/radarr/installation#docker)
|
||||

|
||||
[](#backers)
|
||||
[](#sponsors)
|
||||
|
||||
@@ -9,18 +9,18 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '5.9.0'
|
||||
majorVersion: '5.0.3'
|
||||
minorVersion: $[counter('minorVersion', 2000)]
|
||||
radarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
||||
sentryOrg: 'servarr'
|
||||
sentryUrl: 'https://sentry.servarr.com'
|
||||
dotnetVersion: '6.0.424'
|
||||
nodeVersion: '20.X'
|
||||
innoVersion: '6.2.2'
|
||||
dotnetVersion: '6.0.413'
|
||||
nodeVersion: '16.X'
|
||||
innoVersion: '6.2.0'
|
||||
windowsImage: 'windows-2022'
|
||||
linuxImage: 'ubuntu-20.04'
|
||||
macImage: 'macOS-12'
|
||||
macImage: 'macOS-11'
|
||||
|
||||
trigger:
|
||||
branches:
|
||||
@@ -166,10 +166,10 @@ stages:
|
||||
pool:
|
||||
vmImage: $(imageName)
|
||||
steps:
|
||||
- task: UseNode@1
|
||||
- task: NodeTool@0
|
||||
displayName: Set Node.js version
|
||||
inputs:
|
||||
version: $(nodeVersion)
|
||||
versionSpec: $(nodeVersion)
|
||||
- checkout: self
|
||||
submodules: true
|
||||
fetchDepth: 1
|
||||
@@ -1089,10 +1089,10 @@ stages:
|
||||
pool:
|
||||
vmImage: $(imageName)
|
||||
steps:
|
||||
- task: UseNode@1
|
||||
- task: NodeTool@0
|
||||
displayName: Set Node.js version
|
||||
inputs:
|
||||
version: $(nodeVersion)
|
||||
versionSpec: $(nodeVersion)
|
||||
- checkout: self
|
||||
submodules: true
|
||||
fetchDepth: 1
|
||||
@@ -1116,7 +1116,7 @@ stages:
|
||||
vmImage: ${{ variables.windowsImage }}
|
||||
steps:
|
||||
- checkout: self # Need history for Sonar analysis
|
||||
- task: SonarCloudPrepare@2
|
||||
- task: SonarCloudPrepare@1
|
||||
env:
|
||||
SONAR_SCANNER_OPTS: ''
|
||||
inputs:
|
||||
@@ -1128,7 +1128,7 @@ stages:
|
||||
cliProjectName: 'RadarrUI'
|
||||
cliProjectVersion: '$(radarrVersion)'
|
||||
cliSources: './frontend'
|
||||
- task: SonarCloudAnalyze@2
|
||||
- task: SonarCloudAnalyze@1
|
||||
|
||||
- job: Api_Docs
|
||||
displayName: API Docs
|
||||
@@ -1205,7 +1205,7 @@ stages:
|
||||
submodules: true
|
||||
- powershell: Set-Service SCardSvr -StartupType Manual
|
||||
displayName: Enable Windows Test Service
|
||||
- task: SonarCloudPrepare@2
|
||||
- task: SonarCloudPrepare@1
|
||||
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
||||
inputs:
|
||||
SonarCloud: 'SonarCloud'
|
||||
@@ -1223,21 +1223,25 @@ stages:
|
||||
./build.sh --backend -f net6.0 -r win-x64
|
||||
TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage
|
||||
displayName: Coverage Unit Tests
|
||||
- task: SonarCloudAnalyze@2
|
||||
- task: SonarCloudAnalyze@1
|
||||
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
||||
displayName: Publish SonarCloud Results
|
||||
- task: reportgenerator@5
|
||||
- task: reportgenerator@4
|
||||
displayName: Generate Coverage Report
|
||||
inputs:
|
||||
reports: '$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml'
|
||||
targetdir: '$(Build.SourcesDirectory)/CoverageResults/combined'
|
||||
reporttypes: 'HtmlInline_AzurePipelines;Cobertura;Badges'
|
||||
publishCodeCoverageResults: true
|
||||
- task: PublishCodeCoverageResults@1
|
||||
displayName: Publish Coverage Report
|
||||
inputs:
|
||||
codeCoverageTool: 'cobertura'
|
||||
summaryFileLocation: './CoverageResults/combined/Cobertura.xml'
|
||||
reportDirectory: './CoverageResults/combined/'
|
||||
|
||||
- stage: Report_Out
|
||||
dependsOn:
|
||||
- Analyze
|
||||
- Installer
|
||||
- Unit_Test
|
||||
- Integration
|
||||
- Automation
|
||||
|
||||
2
build.sh
2
build.sh
@@ -254,7 +254,7 @@ InstallInno()
|
||||
ProgressStart "Installing portable Inno Setup"
|
||||
|
||||
rm -rf _inno
|
||||
curl -s --output innosetup.exe "https://files.jrsoftware.org/is/6/innosetup-${INNOVERSION:-6.2.2}.exe"
|
||||
curl -s --output innosetup.exe "https://files.jrsoftware.org/is/6/innosetup-${INNOVERSION:-6.2.0}.exe"
|
||||
mkdir _inno
|
||||
./innosetup.exe //portable=1 //silent //currentuser //dir=.\\_inno
|
||||
rm innosetup.exe
|
||||
|
||||
10
docs.sh
10
docs.sh
@@ -21,21 +21,15 @@ slnFile=src/Radarr.sln
|
||||
|
||||
platform=Posix
|
||||
|
||||
if [ "$PLATFORM" = "Windows" ]; then
|
||||
application=Radarr.Console.dll
|
||||
else
|
||||
application=Radarr.dll
|
||||
fi
|
||||
|
||||
dotnet clean $slnFile -c Debug
|
||||
dotnet clean $slnFile -c Release
|
||||
|
||||
dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p:RuntimeIdentifiers=$RUNTIME -t:PublishAllRids
|
||||
|
||||
dotnet new tool-manifest
|
||||
dotnet tool install --version 6.6.2 Swashbuckle.AspNetCore.Cli
|
||||
dotnet tool install --version 6.5.0 Swashbuckle.AspNetCore.Cli
|
||||
|
||||
dotnet tool run swagger tofile --output ./src/Radarr.Api.V3/openapi.json "$outputFolder/net6.0/$RUNTIME/$application" v3 &
|
||||
dotnet tool run swagger tofile --output ./src/Radarr.Api.V3/openapi.json "$outputFolder/net6.0/$RUNTIME/radarr.console.dll" v3 &
|
||||
|
||||
sleep 45
|
||||
|
||||
|
||||
@@ -359,16 +359,11 @@ module.exports = {
|
||||
],
|
||||
|
||||
rules: Object.assign(typescriptEslintRecommended.rules, {
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
args: 'after-used',
|
||||
argsIgnorePattern: '^_',
|
||||
ignoreRestSiblings: true
|
||||
}
|
||||
],
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'no-shadow': 'off',
|
||||
// These should be enabled after cleaning things up
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'react/prop-types': 'off',
|
||||
'prettier/prettier': 'error',
|
||||
'simple-import-sort/imports': [
|
||||
'error',
|
||||
@@ -381,41 +376,7 @@ module.exports = {
|
||||
['^@?\\w', `^(${dirs})(/.*|$)`, '^\\.', '^\\..*css$']
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
// React Hooks
|
||||
'react-hooks/rules-of-hooks': 'error',
|
||||
'react-hooks/exhaustive-deps': 'error',
|
||||
|
||||
// React
|
||||
'react/function-component-definition': 'error',
|
||||
'react/hook-use-state': 'error',
|
||||
'react/jsx-boolean-value': ['error', 'always'],
|
||||
'react/jsx-curly-brace-presence': [
|
||||
'error',
|
||||
{ props: 'never', children: 'never' }
|
||||
],
|
||||
'react/jsx-fragments': 'error',
|
||||
'react/jsx-handler-names': [
|
||||
'error',
|
||||
{
|
||||
eventHandlerPrefix: 'on',
|
||||
eventHandlerPropPrefix: 'on'
|
||||
}
|
||||
],
|
||||
'react/jsx-no-bind': ['error', { ignoreRefs: true }],
|
||||
'react/jsx-no-useless-fragment': ['error', { allowExpressions: true }],
|
||||
'react/jsx-pascal-case': ['error', { allowAllCaps: true }],
|
||||
'react/jsx-sort-props': [
|
||||
'error',
|
||||
{
|
||||
callbacksLast: true,
|
||||
noSortAlphabetically: true,
|
||||
reservedFirst: true
|
||||
}
|
||||
],
|
||||
'react/prop-types': 'off',
|
||||
'react/self-closing-comp': 'error'
|
||||
]
|
||||
})
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,8 +2,6 @@ const loose = true;
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
'@babel/plugin-transform-logical-assignment-operators',
|
||||
|
||||
// Stage 1
|
||||
'@babel/plugin-proposal-export-default-from',
|
||||
['@babel/plugin-transform-optional-chaining', { loose }],
|
||||
|
||||
@@ -66,7 +66,7 @@ module.exports = (env) => {
|
||||
output: {
|
||||
path: distFolder,
|
||||
publicPath: '/',
|
||||
filename: isProduction ? '[name]-[contenthash].js' : '[name].js',
|
||||
filename: '[name]-[contenthash].js',
|
||||
sourceMapFilename: '[file].map'
|
||||
},
|
||||
|
||||
@@ -91,7 +91,7 @@ module.exports = (env) => {
|
||||
|
||||
new MiniCssExtractPlugin({
|
||||
filename: 'Content/styles.css',
|
||||
chunkFilename: isProduction ? 'Content/[id]-[chunkhash].css' : 'Content/[id].css'
|
||||
chunkFilename: 'Content/[id]-[chunkhash].css'
|
||||
}),
|
||||
|
||||
new HtmlWebpackPlugin({
|
||||
@@ -201,7 +201,7 @@ module.exports = (env) => {
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
modules: {
|
||||
localIdentName: isProduction ? '[name]/[local]/[hash:base64:5]' : '[name]/[local]'
|
||||
localIdentName: '[name]/[local]/[hash:base64:5]'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2,7 +2,6 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
@@ -21,7 +20,6 @@ import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||
import removeOldSelectedState from 'Utilities/Table/removeOldSelectedState';
|
||||
import selectAll from 'Utilities/Table/selectAll';
|
||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||
import BlocklistFilterModal from './BlocklistFilterModal';
|
||||
import BlocklistRowConnector from './BlocklistRowConnector';
|
||||
|
||||
class Blocklist extends Component {
|
||||
@@ -38,7 +36,6 @@ class Blocklist extends Component {
|
||||
lastToggled: null,
|
||||
selectedState: {},
|
||||
isConfirmRemoveModalOpen: false,
|
||||
isConfirmClearModalOpen: false,
|
||||
items: props.items
|
||||
};
|
||||
}
|
||||
@@ -93,19 +90,6 @@ class Blocklist extends Component {
|
||||
this.setState({ isConfirmRemoveModalOpen: false });
|
||||
};
|
||||
|
||||
onClearBlocklistPress = () => {
|
||||
this.setState({ isConfirmClearModalOpen: true });
|
||||
};
|
||||
|
||||
onClearBlocklistConfirmed = () => {
|
||||
this.props.onClearBlocklistPress();
|
||||
this.setState({ isConfirmClearModalOpen: false });
|
||||
};
|
||||
|
||||
onConfirmClearModalClose = () => {
|
||||
this.setState({ isConfirmClearModalOpen: false });
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
@@ -116,13 +100,10 @@ class Blocklist extends Component {
|
||||
error,
|
||||
items,
|
||||
columns,
|
||||
selectedFilterKey,
|
||||
filters,
|
||||
customFilters,
|
||||
totalRecords,
|
||||
isRemoving,
|
||||
isClearingBlocklistExecuting,
|
||||
onFilterSelect,
|
||||
onClearBlocklistPress,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
@@ -130,8 +111,7 @@ class Blocklist extends Component {
|
||||
allSelected,
|
||||
allUnselected,
|
||||
selectedState,
|
||||
isConfirmRemoveModalOpen,
|
||||
isConfirmClearModalOpen
|
||||
isConfirmRemoveModalOpen
|
||||
} = this.state;
|
||||
|
||||
const selectedIds = this.getSelectedIds();
|
||||
@@ -151,9 +131,8 @@ class Blocklist extends Component {
|
||||
<PageToolbarButton
|
||||
label={translate('Clear')}
|
||||
iconName={icons.CLEAR}
|
||||
isDisabled={!items.length}
|
||||
isSpinning={isClearingBlocklistExecuting}
|
||||
onPress={this.onClearBlocklistPress}
|
||||
onPress={onClearBlocklistPress}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
|
||||
@@ -167,15 +146,6 @@ class Blocklist extends Component {
|
||||
iconName={icons.TABLE}
|
||||
/>
|
||||
</TableOptionsModalWrapper>
|
||||
|
||||
<FilterMenu
|
||||
alignMenu={align.RIGHT}
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
filters={filters}
|
||||
customFilters={customFilters}
|
||||
filterModalConnectorComponent={BlocklistFilterModal}
|
||||
onFilterSelect={onFilterSelect}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
</PageToolbar>
|
||||
|
||||
@@ -195,11 +165,7 @@ class Blocklist extends Component {
|
||||
{
|
||||
isPopulated && !error && !items.length &&
|
||||
<Alert kind={kinds.INFO}>
|
||||
{
|
||||
selectedFilterKey === 'all' ?
|
||||
translate('NoHistoryBlocklist') :
|
||||
translate('BlocklistFilterHasNoItems')
|
||||
}
|
||||
{translate('NoHistoryBlocklist')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
@@ -249,16 +215,6 @@ class Blocklist extends Component {
|
||||
onConfirm={this.onRemoveSelectedConfirmed}
|
||||
onCancel={this.onConfirmRemoveModalClose}
|
||||
/>
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={isConfirmClearModalOpen}
|
||||
kind={kinds.DANGER}
|
||||
title={translate('ClearBlocklist')}
|
||||
message={translate('ClearBlocklistMessageText')}
|
||||
confirmLabel={translate('Clear')}
|
||||
onConfirm={this.onClearBlocklistConfirmed}
|
||||
onCancel={this.onConfirmClearModalClose}
|
||||
/>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
@@ -270,15 +226,11 @@ Blocklist.propTypes = {
|
||||
error: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
totalRecords: PropTypes.number,
|
||||
isRemoving: PropTypes.bool.isRequired,
|
||||
isClearingBlocklistExecuting: PropTypes.bool.isRequired,
|
||||
onRemoveSelected: PropTypes.func.isRequired,
|
||||
onClearBlocklistPress: PropTypes.func.isRequired,
|
||||
onFilterSelect: PropTypes.func.isRequired
|
||||
onClearBlocklistPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default Blocklist;
|
||||
|
||||
@@ -6,7 +6,6 @@ import * as commandNames from 'Commands/commandNames';
|
||||
import withCurrentPage from 'Components/withCurrentPage';
|
||||
import * as blocklistActions from 'Store/Actions/blocklistActions';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
||||
import Blocklist from './Blocklist';
|
||||
@@ -14,12 +13,10 @@ import Blocklist from './Blocklist';
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.blocklist,
|
||||
createCustomFiltersSelector('blocklist'),
|
||||
createCommandExecutingSelector(commandNames.CLEAR_BLOCKLIST),
|
||||
(blocklist, customFilters, isClearingBlocklistExecuting) => {
|
||||
(blocklist, isClearingBlocklistExecuting) => {
|
||||
return {
|
||||
isClearingBlocklistExecuting,
|
||||
customFilters,
|
||||
...blocklist
|
||||
};
|
||||
}
|
||||
@@ -100,14 +97,6 @@ class BlocklistConnector extends Component {
|
||||
this.props.setBlocklistSort({ sortKey });
|
||||
};
|
||||
|
||||
onFilterSelect = (selectedFilterKey) => {
|
||||
this.props.setBlocklistFilter({ selectedFilterKey });
|
||||
};
|
||||
|
||||
onClearBlocklistPress = () => {
|
||||
this.props.executeCommand({ name: commandNames.CLEAR_BLOCKLIST });
|
||||
};
|
||||
|
||||
onTableOptionChange = (payload) => {
|
||||
this.props.setBlocklistTableOption(payload);
|
||||
|
||||
@@ -116,6 +105,10 @@ class BlocklistConnector extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
onClearBlocklistPress = () => {
|
||||
this.props.executeCommand({ name: commandNames.CLEAR_BLOCKLIST });
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
@@ -129,7 +122,6 @@ class BlocklistConnector extends Component {
|
||||
onPageSelect={this.onPageSelect}
|
||||
onRemoveSelected={this.onRemoveSelected}
|
||||
onSortPress={this.onSortPress}
|
||||
onFilterSelect={this.onFilterSelect}
|
||||
onTableOptionChange={this.onTableOptionChange}
|
||||
onClearBlocklistPress={this.onClearBlocklistPress}
|
||||
{...this.props}
|
||||
@@ -150,7 +142,6 @@ BlocklistConnector.propTypes = {
|
||||
gotoBlocklistPage: PropTypes.func.isRequired,
|
||||
removeBlocklistItems: PropTypes.func.isRequired,
|
||||
setBlocklistSort: PropTypes.func.isRequired,
|
||||
setBlocklistFilter: PropTypes.func.isRequired,
|
||||
setBlocklistTableOption: PropTypes.func.isRequired,
|
||||
clearBlocklist: PropTypes.func.isRequired,
|
||||
executeCommand: PropTypes.func.isRequired
|
||||
|
||||
@@ -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 { setBlocklistFilter } from 'Store/Actions/blocklistActions';
|
||||
|
||||
function createBlocklistSelector() {
|
||||
return createSelector(
|
||||
(state: AppState) => state.blocklist.items,
|
||||
(blocklistItems) => {
|
||||
return blocklistItems;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createFilterBuilderPropsSelector() {
|
||||
return createSelector(
|
||||
(state: AppState) => state.blocklist.filterBuilderProps,
|
||||
(filterBuilderProps) => {
|
||||
return filterBuilderProps;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
interface BlocklistFilterModalProps {
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
export default function BlocklistFilterModal(props: BlocklistFilterModalProps) {
|
||||
const sectionItems = useSelector(createBlocklistSelector());
|
||||
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
|
||||
const customFilterType = 'blocklist';
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const dispatchSetFilter = useCallback(
|
||||
(payload: unknown) => {
|
||||
dispatch(setBlocklistFilter(payload));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<FilterModal
|
||||
// TODO: Don't spread all the props
|
||||
{...props}
|
||||
sectionItems={sectionItems}
|
||||
filterBuilderProps={filterBuilderProps}
|
||||
customFilterType={customFilterType}
|
||||
dispatchSetFilter={dispatchSetFilter}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import MovieFormats from 'Movie/MovieFormats';
|
||||
import MovieLanguages from 'Movie/MovieLanguages';
|
||||
import MovieLanguage from 'Movie/MovieLanguage';
|
||||
import MovieQuality from 'Movie/MovieQuality';
|
||||
import MovieTitleLink from 'Movie/MovieTitleLink';
|
||||
import translate from 'Utilities/String/translate';
|
||||
@@ -104,7 +104,7 @@ class BlocklistRow extends Component {
|
||||
if (name === 'languages') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
<MovieLanguages
|
||||
<MovieLanguage
|
||||
languages={languages}
|
||||
/>
|
||||
</TableRowCell>
|
||||
|
||||
@@ -14,7 +14,6 @@ import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptions
|
||||
import TablePager from 'Components/Table/TablePager';
|
||||
import { align, icons, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import HistoryFilterModal from './HistoryFilterModal';
|
||||
import HistoryRowConnector from './HistoryRowConnector';
|
||||
|
||||
class History extends Component {
|
||||
@@ -34,7 +33,6 @@ class History extends Component {
|
||||
columns,
|
||||
selectedFilterKey,
|
||||
filters,
|
||||
customFilters,
|
||||
totalRecords,
|
||||
onFilterSelect,
|
||||
onFirstPagePress,
|
||||
@@ -72,8 +70,7 @@ class History extends Component {
|
||||
alignMenu={align.RIGHT}
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
filters={filters}
|
||||
customFilters={customFilters}
|
||||
filterModalConnectorComponent={HistoryFilterModal}
|
||||
customFilters={[]}
|
||||
onFilterSelect={onFilterSelect}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
@@ -147,9 +144,8 @@ History.propTypes = {
|
||||
moviesError: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
selectedFilterKey: PropTypes.string.isRequired,
|
||||
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
totalRecords: PropTypes.number,
|
||||
onFilterSelect: PropTypes.func.isRequired,
|
||||
onFirstPagePress: PropTypes.func.isRequired
|
||||
|
||||
@@ -4,7 +4,6 @@ import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import withCurrentPage from 'Components/withCurrentPage';
|
||||
import * as historyActions from 'Store/Actions/historyActions';
|
||||
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
||||
import History from './History';
|
||||
|
||||
@@ -12,13 +11,11 @@ function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.history,
|
||||
(state) => state.movies,
|
||||
createCustomFiltersSelector('history'),
|
||||
(history, movies, customFilters) => {
|
||||
(history, movies) => {
|
||||
return {
|
||||
isMoviesFetching: movies.isFetching,
|
||||
isMoviesPopulated: movies.isPopulated,
|
||||
moviesError: movies.error,
|
||||
customFilters,
|
||||
...history
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { icons, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './HistoryEventTypeCell.css';
|
||||
|
||||
function getIconName(eventType, data) {
|
||||
function getIconName(eventType) {
|
||||
switch (eventType) {
|
||||
case 'grabbed':
|
||||
return icons.DOWNLOADING;
|
||||
@@ -17,7 +17,7 @@ function getIconName(eventType, data) {
|
||||
case 'downloadFailed':
|
||||
return icons.DOWNLOADING;
|
||||
case 'movieFileDeleted':
|
||||
return data.reason === 'MissingFromDisk' ? icons.FILE_MISSING : icons.DELETE;
|
||||
return icons.DELETE;
|
||||
case 'movieFileRenamed':
|
||||
return icons.ORGANIZE;
|
||||
case 'downloadIgnored':
|
||||
@@ -47,7 +47,7 @@ function getTooltip(eventType, data) {
|
||||
case 'downloadFailed':
|
||||
return translate('MovieDownloadFailedTooltip');
|
||||
case 'movieFileDeleted':
|
||||
return data.reason === 'MissingFromDisk' ? translate('MovieFileMissingTooltip') : translate('MovieFileDeletedTooltip');
|
||||
return translate('MovieFileDeletedTooltip');
|
||||
case 'movieFileRenamed':
|
||||
return translate('MovieFileRenamedTooltip');
|
||||
case 'downloadIgnored':
|
||||
@@ -58,7 +58,7 @@ function getTooltip(eventType, data) {
|
||||
}
|
||||
|
||||
function HistoryEventTypeCell({ eventType, data }) {
|
||||
const iconName = getIconName(eventType, data);
|
||||
const iconName = getIconName(eventType);
|
||||
const iconKind = getIconKind(eventType);
|
||||
const tooltip = getTooltip(eventType, data);
|
||||
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
import FilterModal from 'Components/Filter/FilterModal';
|
||||
import { setHistoryFilter } from 'Store/Actions/historyActions';
|
||||
|
||||
function createHistorySelector() {
|
||||
return createSelector(
|
||||
(state: AppState) => state.history.items,
|
||||
(queueItems) => {
|
||||
return queueItems;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createFilterBuilderPropsSelector() {
|
||||
return createSelector(
|
||||
(state: AppState) => state.history.filterBuilderProps,
|
||||
(filterBuilderProps) => {
|
||||
return filterBuilderProps;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
interface HistoryFilterModalProps {
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
export default function HistoryFilterModal(props: HistoryFilterModalProps) {
|
||||
const sectionItems = useSelector(createHistorySelector());
|
||||
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
|
||||
const customFilterType = 'history';
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const dispatchSetFilter = useCallback(
|
||||
(payload: unknown) => {
|
||||
dispatch(setHistoryFilter(payload));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<FilterModal
|
||||
// TODO: Don't spread all the props
|
||||
{...props}
|
||||
sectionItems={sectionItems}
|
||||
filterBuilderProps={filterBuilderProps}
|
||||
customFilterType={customFilterType}
|
||||
dispatchSetFilter={dispatchSetFilter}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import TableRow from 'Components/Table/TableRow';
|
||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||
import { icons, tooltipPositions } from 'Helpers/Props';
|
||||
import MovieFormats from 'Movie/MovieFormats';
|
||||
import MovieLanguages from 'Movie/MovieLanguages';
|
||||
import MovieLanguage from 'Movie/MovieLanguage';
|
||||
import MovieQuality from 'Movie/MovieQuality';
|
||||
import MovieTitleLink from 'Movie/MovieTitleLink';
|
||||
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
|
||||
@@ -113,7 +113,7 @@ class HistoryRow extends Component {
|
||||
if (name === 'languages') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
<MovieLanguages
|
||||
<MovieLanguage
|
||||
languages={languages}
|
||||
/>
|
||||
</TableRowCell>
|
||||
|
||||
@@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||
@@ -22,10 +21,9 @@ import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||
import removeOldSelectedState from 'Utilities/Table/removeOldSelectedState';
|
||||
import selectAll from 'Utilities/Table/selectAll';
|
||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||
import QueueFilterModal from './QueueFilterModal';
|
||||
import QueueOptionsConnector from './QueueOptionsConnector';
|
||||
import QueueRowConnector from './QueueRowConnector';
|
||||
import RemoveQueueItemModal from './RemoveQueueItemModal';
|
||||
import RemoveQueueItemsModal from './RemoveQueueItemsModal';
|
||||
|
||||
class Queue extends Component {
|
||||
|
||||
@@ -155,16 +153,11 @@ class Queue extends Component {
|
||||
isMoviesPopulated,
|
||||
moviesError,
|
||||
columns,
|
||||
selectedFilterKey,
|
||||
filters,
|
||||
customFilters,
|
||||
count,
|
||||
totalRecords,
|
||||
isGrabbing,
|
||||
isRemoving,
|
||||
isRefreshMonitoredDownloadsExecuting,
|
||||
onRefreshPress,
|
||||
onFilterSelect,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
@@ -219,7 +212,6 @@ class Queue extends Component {
|
||||
>
|
||||
<TableOptionsModalWrapper
|
||||
columns={columns}
|
||||
maxPageSize={200}
|
||||
{...otherProps}
|
||||
optionsComponent={QueueOptionsConnector}
|
||||
>
|
||||
@@ -228,15 +220,6 @@ class Queue extends Component {
|
||||
iconName={icons.TABLE}
|
||||
/>
|
||||
</TableOptionsModalWrapper>
|
||||
|
||||
<FilterMenu
|
||||
alignMenu={align.RIGHT}
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
filters={filters}
|
||||
customFilters={customFilters}
|
||||
filterModalConnectorComponent={QueueFilterModal}
|
||||
onFilterSelect={onFilterSelect}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
</PageToolbar>
|
||||
|
||||
@@ -258,11 +241,7 @@ class Queue extends Component {
|
||||
{
|
||||
isAllPopulated && !hasError && !items.length ?
|
||||
<Alert kind={kinds.INFO}>
|
||||
{
|
||||
selectedFilterKey !== 'all' && count > 0 ?
|
||||
translate('QueueFilterHasNoItems') :
|
||||
translate('QueueIsEmpty')
|
||||
}
|
||||
{translate('QueueIsEmpty')}
|
||||
</Alert> :
|
||||
null
|
||||
}
|
||||
@@ -308,16 +287,9 @@ class Queue extends Component {
|
||||
}
|
||||
</PageContentBody>
|
||||
|
||||
<RemoveQueueItemModal
|
||||
<RemoveQueueItemsModal
|
||||
isOpen={isConfirmRemoveModalOpen}
|
||||
selectedCount={selectedCount}
|
||||
canChangeCategory={isConfirmRemoveModalOpen && (
|
||||
selectedIds.every((id) => {
|
||||
const item = items.find((i) => i.id === id);
|
||||
|
||||
return !!(item && item.downloadClientHasPostImportCategory);
|
||||
})
|
||||
)}
|
||||
canIgnore={isConfirmRemoveModalOpen && (
|
||||
selectedIds.every((id) => {
|
||||
const item = items.find((i) => i.id === id);
|
||||
@@ -325,7 +297,7 @@ class Queue extends Component {
|
||||
return !!(item && item.movieId);
|
||||
})
|
||||
)}
|
||||
pending={isConfirmRemoveModalOpen && (
|
||||
allPending={isConfirmRemoveModalOpen && (
|
||||
selectedIds.every((id) => {
|
||||
const item = items.find((i) => i.id === id);
|
||||
|
||||
@@ -353,22 +325,13 @@ Queue.propTypes = {
|
||||
moviesError: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
count: PropTypes.number.isRequired,
|
||||
totalRecords: PropTypes.number,
|
||||
isGrabbing: PropTypes.bool.isRequired,
|
||||
isRemoving: PropTypes.bool.isRequired,
|
||||
isRefreshMonitoredDownloadsExecuting: PropTypes.bool.isRequired,
|
||||
onRefreshPress: PropTypes.func.isRequired,
|
||||
onGrabSelectedPress: PropTypes.func.isRequired,
|
||||
onRemoveSelectedPress: PropTypes.func.isRequired,
|
||||
onFilterSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
Queue.defaultProps = {
|
||||
count: 0
|
||||
onRemoveSelectedPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default Queue;
|
||||
|
||||
@@ -6,7 +6,6 @@ import * as commandNames from 'Commands/commandNames';
|
||||
import withCurrentPage from 'Components/withCurrentPage';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import * as queueActions from 'Store/Actions/queueActions';
|
||||
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
||||
import Queue from './Queue';
|
||||
@@ -16,16 +15,12 @@ function createMapStateToProps() {
|
||||
(state) => state.movies,
|
||||
(state) => state.queue.options,
|
||||
(state) => state.queue.paged,
|
||||
(state) => state.queue.status.item,
|
||||
createCustomFiltersSelector('queue'),
|
||||
createCommandExecutingSelector(commandNames.REFRESH_MONITORED_DOWNLOADS),
|
||||
(movies, options, queue, status, customFilters, isRefreshMonitoredDownloadsExecuting) => {
|
||||
(movies, options, queue, isRefreshMonitoredDownloadsExecuting) => {
|
||||
return {
|
||||
count: options.includeUnknownMovieItems ? status.totalCount : status.count,
|
||||
isMoviesFetching: movies.isFetching,
|
||||
isMoviesPopulated: movies.isPopulated,
|
||||
moviesError: movies.error,
|
||||
customFilters,
|
||||
isRefreshMonitoredDownloadsExecuting,
|
||||
...options,
|
||||
...queue
|
||||
@@ -111,10 +106,6 @@ class QueueConnector extends Component {
|
||||
this.props.setQueueSort({ sortKey });
|
||||
};
|
||||
|
||||
onFilterSelect = (selectedFilterKey) => {
|
||||
this.props.setQueueFilter({ selectedFilterKey });
|
||||
};
|
||||
|
||||
onTableOptionChange = (payload) => {
|
||||
this.props.setQueueTableOption(payload);
|
||||
|
||||
@@ -149,7 +140,6 @@ class QueueConnector extends Component {
|
||||
onLastPagePress={this.onLastPagePress}
|
||||
onPageSelect={this.onPageSelect}
|
||||
onSortPress={this.onSortPress}
|
||||
onFilterSelect={this.onFilterSelect}
|
||||
onTableOptionChange={this.onTableOptionChange}
|
||||
onRefreshPress={this.onRefreshPress}
|
||||
onGrabSelectedPress={this.onGrabSelectedPress}
|
||||
@@ -172,7 +162,6 @@ QueueConnector.propTypes = {
|
||||
gotoQueueLastPage: PropTypes.func.isRequired,
|
||||
gotoQueuePage: PropTypes.func.isRequired,
|
||||
setQueueSort: PropTypes.func.isRequired,
|
||||
setQueueFilter: PropTypes.func.isRequired,
|
||||
setQueueTableOption: PropTypes.func.isRequired,
|
||||
clearQueue: PropTypes.func.isRequired,
|
||||
grabQueueItems: PropTypes.func.isRequired,
|
||||
|
||||
@@ -81,9 +81,4 @@ QueueDetails.propTypes = {
|
||||
progressBar: PropTypes.node.isRequired
|
||||
};
|
||||
|
||||
QueueDetails.defaultProps = {
|
||||
trackedDownloadStatus: 'ok',
|
||||
trackedDownloadState: 'downloading'
|
||||
};
|
||||
|
||||
export default QueueDetails;
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
import FilterModal from 'Components/Filter/FilterModal';
|
||||
import { setQueueFilter } from 'Store/Actions/queueActions';
|
||||
|
||||
function createQueueSelector() {
|
||||
return createSelector(
|
||||
(state: AppState) => state.queue.paged.items,
|
||||
(queueItems) => {
|
||||
return queueItems;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createFilterBuilderPropsSelector() {
|
||||
return createSelector(
|
||||
(state: AppState) => state.queue.paged.filterBuilderProps,
|
||||
(filterBuilderProps) => {
|
||||
return filterBuilderProps;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
interface QueueFilterModalProps {
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
export default function QueueFilterModal(props: QueueFilterModalProps) {
|
||||
const sectionItems = useSelector(createQueueSelector());
|
||||
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
|
||||
const customFilterType = 'queue';
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const dispatchSetFilter = useCallback(
|
||||
(payload: unknown) => {
|
||||
dispatch(setQueueFilter(payload));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<FilterModal
|
||||
// TODO: Don't spread all the props
|
||||
{...props}
|
||||
sectionItems={sectionItems}
|
||||
filterBuilderProps={filterBuilderProps}
|
||||
customFilterType={customFilterType}
|
||||
dispatchSetFilter={dispatchSetFilter}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -26,5 +26,4 @@
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 70px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||
import ProgressBar from 'Components/ProgressBar';
|
||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||
// import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
@@ -12,7 +12,7 @@ import Tooltip from 'Components/Tooltip/Tooltip';
|
||||
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
||||
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
||||
import MovieFormats from 'Movie/MovieFormats';
|
||||
import MovieLanguages from 'Movie/MovieLanguages';
|
||||
import MovieLanguage from 'Movie/MovieLanguage';
|
||||
import MovieQuality from 'Movie/MovieQuality';
|
||||
import MovieTitleLink from 'Movie/MovieTitleLink';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
@@ -96,9 +96,7 @@ class QueueRow extends Component {
|
||||
indexer,
|
||||
outputPath,
|
||||
downloadClient,
|
||||
downloadClientHasPostImportCategory,
|
||||
estimatedCompletionTime,
|
||||
added,
|
||||
timeleft,
|
||||
size,
|
||||
sizeleft,
|
||||
@@ -175,7 +173,7 @@ class QueueRow extends Component {
|
||||
if (name === 'languages') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
<MovieLanguages
|
||||
<MovieLanguage
|
||||
languages={languages}
|
||||
/>
|
||||
</TableRowCell>
|
||||
@@ -317,15 +315,6 @@ class QueueRow extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'added') {
|
||||
return (
|
||||
<RelativeDateCellConnector
|
||||
key={name}
|
||||
date={added}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'actions') {
|
||||
return (
|
||||
<TableRowCell
|
||||
@@ -374,7 +363,6 @@ class QueueRow extends Component {
|
||||
<RemoveQueueItemModal
|
||||
isOpen={isRemoveQueueItemModalOpen}
|
||||
sourceTitle={title}
|
||||
canChangeCategory={!!downloadClientHasPostImportCategory}
|
||||
canIgnore={!!movie}
|
||||
isPending={isPending}
|
||||
onRemovePress={this.onRemoveQueueItemModalConfirmed}
|
||||
@@ -404,9 +392,7 @@ QueueRow.propTypes = {
|
||||
indexer: PropTypes.string,
|
||||
outputPath: PropTypes.string,
|
||||
downloadClient: PropTypes.string,
|
||||
downloadClientHasPostImportCategory: PropTypes.bool,
|
||||
estimatedCompletionTime: PropTypes.string,
|
||||
added: PropTypes.string,
|
||||
timeleft: PropTypes.string,
|
||||
size: PropTypes.number,
|
||||
year: PropTypes.number,
|
||||
|
||||
@@ -70,11 +70,6 @@ function QueueStatus(props) {
|
||||
iconName = icons.DOWNLOADED;
|
||||
title = translate('Downloaded');
|
||||
|
||||
if (trackedDownloadState === 'importBlocked') {
|
||||
title += ` - ${translate('UnableToImportAutomatically')}`;
|
||||
iconKind = kinds.WARNING;
|
||||
}
|
||||
|
||||
if (trackedDownloadState === 'importPending') {
|
||||
title += ` - ${translate('WaitingToImport')}`;
|
||||
iconKind = kinds.PURPLE;
|
||||
|
||||
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import { tooltipPositions } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import QueueStatus from './QueueStatus';
|
||||
import styles from './QueueStatusCell.css';
|
||||
|
||||
@@ -40,8 +41,8 @@ QueueStatusCell.propTypes = {
|
||||
};
|
||||
|
||||
QueueStatusCell.defaultProps = {
|
||||
trackedDownloadStatus: 'ok',
|
||||
trackedDownloadState: 'downloading'
|
||||
trackedDownloadStatus: translate('Ok'),
|
||||
trackedDownloadState: translate('Downloading')
|
||||
};
|
||||
|
||||
export default QueueStatusCell;
|
||||
|
||||
171
frontend/src/Activity/Queue/RemoveQueueItemModal.js
Normal file
171
frontend/src/Activity/Queue/RemoveQueueItemModal.js
Normal file
@@ -0,0 +1,171 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import Button from 'Components/Link/Button';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
class RemoveQueueItemModal extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
remove: true,
|
||||
blocklist: false,
|
||||
skipRedownload: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
resetState = function() {
|
||||
this.setState({
|
||||
remove: true,
|
||||
blocklist: false,
|
||||
skipRedownload: false
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onRemoveChange = ({ value }) => {
|
||||
this.setState({ remove: value });
|
||||
};
|
||||
|
||||
onBlocklistChange = ({ value }) => {
|
||||
this.setState({ blocklist: value });
|
||||
};
|
||||
|
||||
onSkipRedownloadChange = ({ value }) => {
|
||||
this.setState({ skipRedownload: value });
|
||||
};
|
||||
|
||||
onRemoveConfirmed = () => {
|
||||
const state = this.state;
|
||||
|
||||
this.resetState();
|
||||
this.props.onRemovePress(state);
|
||||
};
|
||||
|
||||
onModalClose = () => {
|
||||
this.resetState();
|
||||
this.props.onModalClose();
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
isOpen,
|
||||
sourceTitle,
|
||||
canIgnore,
|
||||
isPending
|
||||
} = this.props;
|
||||
|
||||
const { remove, blocklist, skipRedownload } = this.state;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
size={sizes.MEDIUM}
|
||||
onModalClose={this.onModalClose}
|
||||
>
|
||||
<ModalContent
|
||||
onModalClose={this.onModalClose}
|
||||
>
|
||||
<ModalHeader>
|
||||
{translate('RemoveQueueItem', { sourceTitle })}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<div>
|
||||
{translate('RemoveQueueItemConfirmation', { sourceTitle })}
|
||||
</div>
|
||||
|
||||
{
|
||||
isPending ?
|
||||
null :
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="remove"
|
||||
value={remove}
|
||||
helpTextWarning={translate('RemoveFromDownloadClientHelpTextWarning')}
|
||||
isDisabled={!canIgnore}
|
||||
onChange={this.onRemoveChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
}
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('BlocklistRelease')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="blocklist"
|
||||
value={blocklist}
|
||||
helpText={translate('BlocklistReleaseHelpText')}
|
||||
onChange={this.onBlocklistChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{
|
||||
blocklist ?
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('SkipRedownload')}</FormLabel>
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="skipRedownload"
|
||||
value={skipRedownload}
|
||||
helpText={translate('SkipRedownloadHelpText')}
|
||||
onChange={this.onSkipRedownloadChange}
|
||||
/>
|
||||
</FormGroup> :
|
||||
null
|
||||
}
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={this.onModalClose}>
|
||||
{translate('Close')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
kind={kinds.DANGER}
|
||||
onPress={this.onRemoveConfirmed}
|
||||
>
|
||||
{translate('Remove')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RemoveQueueItemModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
sourceTitle: PropTypes.string.isRequired,
|
||||
canIgnore: PropTypes.bool.isRequired,
|
||||
isPending: PropTypes.bool.isRequired,
|
||||
onRemovePress: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default RemoveQueueItemModal;
|
||||
@@ -1,231 +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'),
|
||||
isDisabled: isPending,
|
||||
hint: multipleSelected
|
||||
? translate('BlocklistAndSearchMultipleHint')
|
||||
: translate('BlocklistAndSearchHint'),
|
||||
},
|
||||
{
|
||||
key: 'blocklistOnly',
|
||||
value: translate('BlocklistOnly'),
|
||||
hint: multipleSelected
|
||||
? translate('BlocklistMultipleOnlyHint')
|
||||
: translate('BlocklistOnlyHint'),
|
||||
},
|
||||
];
|
||||
}, [isPending, multipleSelected]);
|
||||
|
||||
const handleRemovalMethodChange = useCallback(
|
||||
({ value }: { value: RemovalMethod }) => {
|
||||
setRemovalMethod(value);
|
||||
},
|
||||
[setRemovalMethod]
|
||||
);
|
||||
|
||||
const handleBlocklistMethodChange = useCallback(
|
||||
({ value }: { value: BlocklistMethod }) => {
|
||||
setBlocklistMethod(value);
|
||||
},
|
||||
[setBlocklistMethod]
|
||||
);
|
||||
|
||||
const handleConfirmRemove = useCallback(() => {
|
||||
onRemovePress({
|
||||
remove: removalMethod === 'removeFromClient',
|
||||
changeCategory: removalMethod === 'changeCategory',
|
||||
blocklist: blocklistMethod !== 'doNotBlocklist',
|
||||
skipRedownload: blocklistMethod === 'blocklistOnly',
|
||||
});
|
||||
|
||||
setRemovalMethod('removeFromClient');
|
||||
setBlocklistMethod('doNotBlocklist');
|
||||
}, [
|
||||
removalMethod,
|
||||
blocklistMethod,
|
||||
setRemovalMethod,
|
||||
setBlocklistMethod,
|
||||
onRemovePress,
|
||||
]);
|
||||
|
||||
const handleModalClose = useCallback(() => {
|
||||
setRemovalMethod('removeFromClient');
|
||||
setBlocklistMethod('doNotBlocklist');
|
||||
|
||||
onModalClose();
|
||||
}, [setRemovalMethod, setBlocklistMethod, onModalClose]);
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} size={sizes.MEDIUM} onModalClose={handleModalClose}>
|
||||
<ModalContent onModalClose={handleModalClose}>
|
||||
<ModalHeader>{title}</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<div className={styles.message}>{message}</div>
|
||||
|
||||
{isPending ? null : (
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('RemoveQueueItemRemovalMethod')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name="removalMethod"
|
||||
value={removalMethod}
|
||||
values={removalMethodOptions}
|
||||
isDisabled={!canChangeCategory && !canIgnore}
|
||||
helpTextWarning={translate(
|
||||
'RemoveQueueItemRemovalMethodHelpTextWarning'
|
||||
)}
|
||||
onChange={handleRemovalMethodChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>
|
||||
{multipleSelected
|
||||
? translate('BlocklistReleases')
|
||||
: translate('BlocklistRelease')}
|
||||
</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name="blocklistMethod"
|
||||
value={blocklistMethod}
|
||||
values={blocklistMethodOptions}
|
||||
helpText={translate('BlocklistReleaseHelpText')}
|
||||
onChange={handleBlocklistMethodChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={handleModalClose}>{translate('Close')}</Button>
|
||||
|
||||
<Button kind={kinds.DANGER} onPress={handleConfirmRemove}>
|
||||
{translate('Remove')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default RemoveQueueItemModal;
|
||||
174
frontend/src/Activity/Queue/RemoveQueueItemsModal.js
Normal file
174
frontend/src/Activity/Queue/RemoveQueueItemsModal.js
Normal file
@@ -0,0 +1,174 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import Button from 'Components/Link/Button';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './RemoveQueueItemsModal.css';
|
||||
|
||||
class RemoveQueueItemsModal extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
remove: true,
|
||||
blocklist: false,
|
||||
skipRedownload: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
resetState = function() {
|
||||
this.setState({
|
||||
remove: true,
|
||||
blocklist: false,
|
||||
skipRedownload: false
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onRemoveChange = ({ value }) => {
|
||||
this.setState({ remove: value });
|
||||
};
|
||||
|
||||
onBlocklistChange = ({ value }) => {
|
||||
this.setState({ blocklist: value });
|
||||
};
|
||||
|
||||
onSkipRedownloadChange = ({ value }) => {
|
||||
this.setState({ skipRedownload: value });
|
||||
};
|
||||
|
||||
onRemoveConfirmed = () => {
|
||||
const state = this.state;
|
||||
|
||||
this.resetState();
|
||||
this.props.onRemovePress(state);
|
||||
};
|
||||
|
||||
onModalClose = () => {
|
||||
this.resetState();
|
||||
this.props.onModalClose();
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
isOpen,
|
||||
selectedCount,
|
||||
canIgnore,
|
||||
allPending
|
||||
} = this.props;
|
||||
|
||||
const { remove, blocklist, skipRedownload } = this.state;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
size={sizes.MEDIUM}
|
||||
onModalClose={this.onModalClose}
|
||||
>
|
||||
<ModalContent
|
||||
onModalClose={this.onModalClose}
|
||||
>
|
||||
<ModalHeader>
|
||||
{selectedCount > 1 ? translate('RemoveSelectedItems') : translate('RemoveSelectedItem')}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<div className={styles.message}>
|
||||
{selectedCount > 1 ? translate('RemoveSelectedItemsQueueMessageText', { selectedCount }) : translate('RemoveSelectedItemQueueMessageText')}
|
||||
</div>
|
||||
|
||||
{
|
||||
allPending ?
|
||||
null :
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="remove"
|
||||
value={remove}
|
||||
helpTextWarning={translate('RemoveHelpTextWarning')}
|
||||
isDisabled={!canIgnore}
|
||||
onChange={this.onRemoveChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
}
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>
|
||||
{selectedCount > 1 ? translate('BlocklistReleases') : translate('BlocklistRelease')}
|
||||
</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="blocklist"
|
||||
value={blocklist}
|
||||
helpText={translate('BlocklistReleaseHelpText')}
|
||||
onChange={this.onBlocklistChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{
|
||||
blocklist ?
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('SkipRedownload')}</FormLabel>
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="skipRedownload"
|
||||
value={skipRedownload}
|
||||
helpText={translate('SkipRedownloadHelpText')}
|
||||
onChange={this.onSkipRedownloadChange}
|
||||
/>
|
||||
</FormGroup> :
|
||||
null
|
||||
}
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={this.onModalClose}>
|
||||
{translate('Close')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
kind={kinds.DANGER}
|
||||
onPress={this.onRemoveConfirmed}
|
||||
>
|
||||
{translate('Remove')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RemoveQueueItemsModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
selectedCount: PropTypes.number.isRequired,
|
||||
canIgnore: PropTypes.bool.isRequired,
|
||||
allPending: PropTypes.bool.isRequired,
|
||||
onRemovePress: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default RemoveQueueItemsModal;
|
||||
@@ -1,9 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
||||
import formatTime from 'Utilities/Date/formatTime';
|
||||
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
|
||||
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
||||
@@ -28,13 +25,11 @@ function TimeleftCell(props) {
|
||||
const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true });
|
||||
|
||||
return (
|
||||
<TableRowCell className={styles.timeleft}>
|
||||
<Tooltip
|
||||
anchor={<Icon name={icons.INFO} />}
|
||||
tooltip={translate('DelayingDownloadUntil', { date, time })}
|
||||
kind={kinds.INVERSE}
|
||||
position={tooltipPositions.TOP}
|
||||
/>
|
||||
<TableRowCell
|
||||
className={styles.timeleft}
|
||||
title={translate('DelayingDownloadUntil', { date, time })}
|
||||
>
|
||||
-
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
@@ -44,13 +39,11 @@ function TimeleftCell(props) {
|
||||
const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true });
|
||||
|
||||
return (
|
||||
<TableRowCell className={styles.timeleft}>
|
||||
<Tooltip
|
||||
anchor={<Icon name={icons.INFO} />}
|
||||
tooltip={translate('RetryingDownloadOn', { date, time })}
|
||||
kind={kinds.INVERSE}
|
||||
position={tooltipPositions.TOP}
|
||||
/>
|
||||
<TableRowCell
|
||||
className={styles.timeleft}
|
||||
title={translate('RetryingDownloadOn', { date, time })}
|
||||
>
|
||||
-
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import TextInput from 'Components/Form/TextInput';
|
||||
import Icon from 'Components/Icon';
|
||||
import Button from 'Components/Link/Button';
|
||||
@@ -131,12 +130,7 @@ class AddNewMovie extends Component {
|
||||
<div className={styles.helpText}>
|
||||
{translate('FailedLoadingSearchResults')}
|
||||
</div>
|
||||
<Alert kind={kinds.WARNING}>{getErrorMessage(error)}</Alert>
|
||||
<div>
|
||||
<Link to="https://wiki.servarr.com/radarr/troubleshooting#invalid-response-received-from-tmdb">
|
||||
{translate('WhySearchesCouldBeFailing')}
|
||||
</Link>
|
||||
</div>
|
||||
<div>{getErrorMessage(error)}</div>
|
||||
</div> : null
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,10 @@ import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { clearAddMovie, lookupMovie } from 'Store/Actions/addMovieActions';
|
||||
import { clearMovieFiles, fetchMovieFiles } from 'Store/Actions/movieFileActions';
|
||||
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
|
||||
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||
import { fetchImportExclusions } from 'Store/Actions/Settings/importExclusions';
|
||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
|
||||
import selectUniqueIds from 'Utilities/Object/selectUniqueIds';
|
||||
import parseUrl from 'Utilities/String/parseUrl';
|
||||
import AddNewMovie from './AddNewMovie';
|
||||
|
||||
@@ -35,10 +33,9 @@ const mapDispatchToProps = {
|
||||
lookupMovie,
|
||||
clearAddMovie,
|
||||
fetchRootFolders,
|
||||
fetchImportExclusions,
|
||||
fetchQueueDetails,
|
||||
clearQueueDetails,
|
||||
fetchMovieFiles,
|
||||
clearMovieFiles
|
||||
clearQueueDetails
|
||||
};
|
||||
|
||||
class AddNewMovieConnector extends Component {
|
||||
@@ -54,23 +51,10 @@ class AddNewMovieConnector extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchRootFolders();
|
||||
this.props.fetchImportExclusions();
|
||||
this.props.fetchQueueDetails();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
items
|
||||
} = this.props;
|
||||
|
||||
if (hasDifferentItems(prevProps.items, items)) {
|
||||
const movieIds = selectUniqueIds(items, 'internalId');
|
||||
|
||||
if (movieIds.length) {
|
||||
this.props.fetchMovieFiles({ movieId: movieIds });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this._movieLookupTimeout) {
|
||||
clearTimeout(this._movieLookupTimeout);
|
||||
@@ -78,7 +62,6 @@ class AddNewMovieConnector extends Component {
|
||||
|
||||
this.props.clearAddMovie();
|
||||
this.props.clearQueueDetails();
|
||||
this.props.clearMovieFiles();
|
||||
}
|
||||
|
||||
//
|
||||
@@ -124,14 +107,12 @@ class AddNewMovieConnector extends Component {
|
||||
|
||||
AddNewMovieConnector.propTypes = {
|
||||
term: PropTypes.string,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
lookupMovie: PropTypes.func.isRequired,
|
||||
clearAddMovie: PropTypes.func.isRequired,
|
||||
fetchRootFolders: PropTypes.func.isRequired,
|
||||
fetchImportExclusions: PropTypes.func.isRequired,
|
||||
fetchQueueDetails: PropTypes.func.isRequired,
|
||||
clearQueueDetails: PropTypes.func.isRequired,
|
||||
fetchMovieFiles: PropTypes.func.isRequired,
|
||||
clearMovieFiles: PropTypes.func.isRequired
|
||||
clearQueueDetails: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(AddNewMovieConnector);
|
||||
|
||||
@@ -85,14 +85,8 @@
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.originalLanguage,
|
||||
.studio,
|
||||
.genres {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.links {
|
||||
margin-left: 5px;
|
||||
margin-left: 8px;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,8 @@ interface CssExports {
|
||||
'certification': string;
|
||||
'content': string;
|
||||
'exclusionIcon': string;
|
||||
'genres': string;
|
||||
'icons': string;
|
||||
'links': string;
|
||||
'originalLanguage': string;
|
||||
'overlay': string;
|
||||
'overview': string;
|
||||
'poster': string;
|
||||
@@ -16,7 +14,6 @@ interface CssExports {
|
||||
'runtime': string;
|
||||
'searchResult': string;
|
||||
'statusContainer': string;
|
||||
'studio': string;
|
||||
'title': string;
|
||||
'titleContainer': string;
|
||||
'titleRow': string;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
import ImdbRating from 'Components/ImdbRating';
|
||||
import Label from 'Components/Label';
|
||||
import Link from 'Components/Link/Link';
|
||||
import TmdbRating from 'Components/TmdbRating';
|
||||
@@ -62,23 +61,21 @@ class AddNewMovieSearchResult extends Component {
|
||||
titleSlug,
|
||||
year,
|
||||
studio,
|
||||
originalLanguage,
|
||||
genres,
|
||||
status,
|
||||
overview,
|
||||
ratings,
|
||||
folder,
|
||||
images,
|
||||
existingMovieId,
|
||||
isExistingMovie,
|
||||
isExcluded,
|
||||
isExclusionMovie,
|
||||
isSmallScreen,
|
||||
colorImpairedMode,
|
||||
id,
|
||||
monitored,
|
||||
hasFile,
|
||||
isAvailable,
|
||||
movieFile,
|
||||
queueItem,
|
||||
queueStatus,
|
||||
queueState,
|
||||
runtime,
|
||||
movieRuntimeFormat,
|
||||
certification
|
||||
@@ -88,8 +85,6 @@ class AddNewMovieSearchResult extends Component {
|
||||
isNewAddMovieModalOpen
|
||||
} = this.state;
|
||||
|
||||
const hasMovieFile = !!movieFile;
|
||||
|
||||
const linkProps = isExistingMovie ? { to: `/movie/${titleSlug}` } : { onPress: this.onPress };
|
||||
const posterWidth = 167;
|
||||
const posterHeight = 250;
|
||||
@@ -125,13 +120,13 @@ class AddNewMovieSearchResult extends Component {
|
||||
{
|
||||
isExistingMovie &&
|
||||
<MovieIndexProgressBar
|
||||
movieId={existingMovieId}
|
||||
movieFile={movieFile}
|
||||
monitored={monitored}
|
||||
hasFile={hasMovieFile}
|
||||
hasFile={hasFile}
|
||||
status={status}
|
||||
width={posterWidth}
|
||||
detailedProgressBar={true}
|
||||
queueStatus={queueStatus}
|
||||
queueState={queueState}
|
||||
isAvailable={isAvailable}
|
||||
/>
|
||||
}
|
||||
@@ -167,7 +162,7 @@ class AddNewMovieSearchResult extends Component {
|
||||
}
|
||||
|
||||
{
|
||||
isExcluded &&
|
||||
isExclusionMovie &&
|
||||
<Icon
|
||||
className={styles.exclusionIcon}
|
||||
name={icons.DANGER}
|
||||
@@ -203,56 +198,10 @@ class AddNewMovieSearchResult extends Component {
|
||||
</Label>
|
||||
|
||||
{
|
||||
ratings.imdb ?
|
||||
!!studio &&
|
||||
<Label size={sizes.LARGE}>
|
||||
<ImdbRating
|
||||
ratings={ratings}
|
||||
iconSize={13}
|
||||
/>
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
originalLanguage?.name ?
|
||||
<Label size={sizes.LARGE}>
|
||||
<Icon
|
||||
name={icons.LANGUAGE}
|
||||
size={13}
|
||||
/>
|
||||
<span className={styles.originalLanguage}>
|
||||
{originalLanguage.name}
|
||||
</span>
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
studio ?
|
||||
<Label size={sizes.LARGE}>
|
||||
<Icon
|
||||
name={icons.STUDIO}
|
||||
size={13}
|
||||
/>
|
||||
<span className={styles.studio}>
|
||||
{studio}
|
||||
</span>
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
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
|
||||
{studio}
|
||||
</Label>
|
||||
}
|
||||
|
||||
<Tooltip
|
||||
@@ -266,15 +215,15 @@ class AddNewMovieSearchResult extends Component {
|
||||
/>
|
||||
|
||||
<span className={styles.links}>
|
||||
{translate('Links')}
|
||||
Links
|
||||
</span>
|
||||
</Label>
|
||||
}
|
||||
tooltip={
|
||||
<MovieDetailsLinks
|
||||
tmdbId={tmdbId}
|
||||
imdbId={imdbId}
|
||||
youTubeTrailerId={youTubeTrailerId}
|
||||
imdbId={imdbId}
|
||||
/>
|
||||
}
|
||||
canFlip={true}
|
||||
@@ -285,11 +234,9 @@ class AddNewMovieSearchResult extends Component {
|
||||
{
|
||||
isExistingMovie && isSmallScreen &&
|
||||
<MovieStatusLabel
|
||||
status={status}
|
||||
hasMovieFiles={hasMovieFile}
|
||||
hasMovieFiles={hasFile}
|
||||
monitored={monitored}
|
||||
isAvailable={isAvailable}
|
||||
queueItem={queueItem}
|
||||
id={id}
|
||||
useLabel={true}
|
||||
colorImpairedMode={colorImpairedMode}
|
||||
@@ -326,31 +273,25 @@ AddNewMovieSearchResult.propTypes = {
|
||||
titleSlug: PropTypes.string.isRequired,
|
||||
year: PropTypes.number.isRequired,
|
||||
studio: PropTypes.string,
|
||||
originalLanguage: PropTypes.object,
|
||||
genres: PropTypes.arrayOf(PropTypes.string),
|
||||
status: PropTypes.string.isRequired,
|
||||
overview: PropTypes.string,
|
||||
ratings: PropTypes.object.isRequired,
|
||||
folder: PropTypes.string.isRequired,
|
||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
existingMovieId: PropTypes.number,
|
||||
isExistingMovie: PropTypes.bool.isRequired,
|
||||
isExcluded: PropTypes.bool,
|
||||
isExclusionMovie: PropTypes.bool.isRequired,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
id: PropTypes.number,
|
||||
queueItems: PropTypes.arrayOf(PropTypes.object),
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
hasFile: PropTypes.bool.isRequired,
|
||||
isAvailable: PropTypes.bool.isRequired,
|
||||
movieFile: PropTypes.object,
|
||||
queueItem: PropTypes.object,
|
||||
colorImpairedMode: PropTypes.bool,
|
||||
queueStatus: PropTypes.string,
|
||||
queueState: PropTypes.string,
|
||||
runtime: PropTypes.number.isRequired,
|
||||
movieRuntimeFormat: PropTypes.string.isRequired,
|
||||
certification: PropTypes.string
|
||||
};
|
||||
|
||||
AddNewMovieSearchResult.defaultProps = {
|
||||
genres: [],
|
||||
isExcluded: false
|
||||
};
|
||||
|
||||
export default AddNewMovieSearchResult;
|
||||
|
||||
@@ -1,28 +1,26 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import createExclusionMovieSelector from 'Store/Selectors/createExclusionMovieSelector';
|
||||
import createExistingMovieSelector from 'Store/Selectors/createExistingMovieSelector';
|
||||
import AddNewMovieSearchResult from './AddNewMovieSearchResult';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createExistingMovieSelector(),
|
||||
createExclusionMovieSelector(),
|
||||
createDimensionsSelector(),
|
||||
(state) => state.queue.details.items,
|
||||
(state) => state.movieFiles.items,
|
||||
(state, { internalId }) => internalId,
|
||||
(state) => state.settings.ui.item.movieRuntimeFormat,
|
||||
(isExistingMovie, dimensions, queueItems, movieFiles, internalId, movieRuntimeFormat) => {
|
||||
const queueItem = queueItems.find((item) => internalId > 0 && item.movieId === internalId);
|
||||
const movieFile = movieFiles.find((item) => internalId > 0 && item.movieId === internalId);
|
||||
(isExistingMovie, isExclusionMovie, dimensions, queueItems, internalId) => {
|
||||
const firstQueueItem = queueItems.find((q) => q.movieId === internalId && internalId > 0);
|
||||
|
||||
return {
|
||||
existingMovieId: internalId,
|
||||
isExistingMovie,
|
||||
isExclusionMovie,
|
||||
isSmallScreen: dimensions.isSmallScreen,
|
||||
queueItem,
|
||||
movieFile,
|
||||
movieRuntimeFormat
|
||||
queueStatus: firstQueueItem ? firstQueueItem.status : null,
|
||||
queueState: firstQueueItem ? firstQueueItem.trackedDownloadState : null
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -10,7 +10,6 @@ import styles from './ImportMovieRow.css';
|
||||
function ImportMovieRow(props) {
|
||||
const {
|
||||
id,
|
||||
relativePath,
|
||||
monitor,
|
||||
qualityProfileId,
|
||||
minimumAvailability,
|
||||
@@ -32,7 +31,7 @@ function ImportMovieRow(props) {
|
||||
/>
|
||||
|
||||
<VirtualTableRowCell className={styles.folder}>
|
||||
{relativePath}
|
||||
{id}
|
||||
</VirtualTableRowCell>
|
||||
|
||||
<VirtualTableRowCell className={styles.movie}>
|
||||
@@ -74,7 +73,6 @@ function ImportMovieRow(props) {
|
||||
|
||||
ImportMovieRow.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
relativePath: PropTypes.string.isRequired,
|
||||
monitor: PropTypes.string.isRequired,
|
||||
qualityProfileId: PropTypes.number.isRequired,
|
||||
minimumAvailability: PropTypes.string.isRequired,
|
||||
|
||||
@@ -30,7 +30,7 @@ class ImportMovieTable extends Component {
|
||||
unmappedFolders.forEach((unmappedFolder) => {
|
||||
const id = unmappedFolder.name;
|
||||
|
||||
onMovieLookup(id, unmappedFolder.path, unmappedFolder.relativePath);
|
||||
onMovieLookup(id, unmappedFolder.path);
|
||||
|
||||
onSetImportMovieValue({
|
||||
id,
|
||||
|
||||
@@ -25,11 +25,10 @@ function createMapStateToProps() {
|
||||
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
onMovieLookup(name, path, relativePath) {
|
||||
onMovieLookup(name, path) {
|
||||
dispatch(queueLookupMovie({
|
||||
name,
|
||||
path,
|
||||
relativePath,
|
||||
term: name
|
||||
}));
|
||||
},
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
.contentContainer {
|
||||
z-index: $popperZIndex;
|
||||
margin-top: 4px;
|
||||
/* 400px container width with 8px padding on each side */
|
||||
/* 400px container witdh with 8px padding on each side */
|
||||
width: 384px;
|
||||
}
|
||||
|
||||
|
||||
@@ -148,7 +148,7 @@ class ImportMovieSelectFolder extends Component {
|
||||
className={styles.addErrorAlert}
|
||||
kind={kinds.DANGER}
|
||||
>
|
||||
{translate('AddRootFolderError')}
|
||||
{translate('UnableToAddRootFolder')}
|
||||
|
||||
<ul>
|
||||
{
|
||||
|
||||
@@ -5,13 +5,12 @@ import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { addRootFolder, deleteRootFolder, fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||
import createRootFoldersSelector from 'Store/Selectors/createRootFoldersSelector';
|
||||
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
|
||||
import ImportMovieSelectFolder from './ImportMovieSelectFolder';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createRootFoldersSelector(),
|
||||
(state) => state.rootFolders,
|
||||
createSystemStatusSelector(),
|
||||
(rootFolders, systemStatus) => {
|
||||
return {
|
||||
|
||||
@@ -12,10 +12,11 @@ function App({ store, history }) {
|
||||
<DocumentTitle title={window.Radarr.instanceName}>
|
||||
<Provider store={store}>
|
||||
<ConnectedRouter history={history}>
|
||||
<ApplyTheme />
|
||||
<PageConnector>
|
||||
<AppRoutes app={App} />
|
||||
</PageConnector>
|
||||
<ApplyTheme>
|
||||
<PageConnector>
|
||||
<AppRoutes app={App} />
|
||||
</PageConnector>
|
||||
</ApplyTheme>
|
||||
</ConnectedRouter>
|
||||
</Provider>
|
||||
</DocumentTitle>
|
||||
|
||||
@@ -33,8 +33,6 @@ import Status from 'System/Status/Status';
|
||||
import Tasks from 'System/Tasks/Tasks';
|
||||
import UpdatesConnector from 'System/Updates/UpdatesConnector';
|
||||
import getPathWithUrlBase from 'Utilities/getPathWithUrlBase';
|
||||
import CutoffUnmetConnector from 'Wanted/CutoffUnmet/CutoffUnmetConnector';
|
||||
import MissingConnector from 'Wanted/Missing/MissingConnector';
|
||||
|
||||
function AppRoutes(props) {
|
||||
const {
|
||||
@@ -123,20 +121,6 @@ function AppRoutes(props) {
|
||||
component={BlocklistConnector}
|
||||
/>
|
||||
|
||||
{/*
|
||||
Wanted
|
||||
*/}
|
||||
|
||||
<Route
|
||||
path="/wanted/missing"
|
||||
component={MissingConnector}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/wanted/cutoffunmet"
|
||||
component={CutoffUnmetConnector}
|
||||
/>
|
||||
|
||||
{/*
|
||||
Settings
|
||||
*/}
|
||||
|
||||
@@ -65,12 +65,12 @@ function AppUpdatedModalContent(props) {
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{translate('AppUpdated')}
|
||||
{translate('AppUpdated', { appName: 'Radarr' })}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<div>
|
||||
<InlineMarkdown data={translate('AppUpdatedVersion', { version })} blockClassName={styles.version} />
|
||||
<InlineMarkdown data={translate('AppUpdatedVersion', { appName: 'Radarr', version })} blockClassName={styles.version} />
|
||||
</div>
|
||||
|
||||
{
|
||||
|
||||
49
frontend/src/App/ApplyTheme.js
Normal file
49
frontend/src/App/ApplyTheme.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Fragment, useCallback, useEffect } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import themes from 'Styles/Themes';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.settings.ui.item.theme || window.Radarr.theme,
|
||||
(
|
||||
theme
|
||||
) => {
|
||||
return {
|
||||
theme
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function ApplyTheme({ theme, children }) {
|
||||
// Update the CSS Variables
|
||||
const updateCSSVariables = useCallback(() => {
|
||||
const arrayOfVariableKeys = Object.keys(themes[theme]);
|
||||
const arrayOfVariableValues = Object.values(themes[theme]);
|
||||
|
||||
// Loop through each array key and set the CSS Variables
|
||||
arrayOfVariableKeys.forEach((cssVariableKey, index) => {
|
||||
// Based on our snippet from MDN
|
||||
document.documentElement.style.setProperty(
|
||||
`--${cssVariableKey}`,
|
||||
arrayOfVariableValues[index]
|
||||
);
|
||||
});
|
||||
}, [theme]);
|
||||
|
||||
// On Component Mount and Component Update
|
||||
useEffect(() => {
|
||||
updateCSSVariables(theme);
|
||||
}, [updateCSSVariables, theme]);
|
||||
|
||||
return <Fragment>{children}</Fragment>;
|
||||
}
|
||||
|
||||
ApplyTheme.propTypes = {
|
||||
theme: PropTypes.string.isRequired,
|
||||
children: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps)(ApplyTheme);
|
||||
@@ -1,33 +0,0 @@
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import themes from 'Styles/Themes';
|
||||
import AppState from './State/AppState';
|
||||
|
||||
function createThemeSelector() {
|
||||
return createSelector(
|
||||
(state: AppState) => state.settings.ui.item.theme || window.Radarr.theme,
|
||||
(theme) => {
|
||||
return theme;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function ApplyTheme() {
|
||||
const theme = useSelector(createThemeSelector());
|
||||
|
||||
const updateCSSVariables = useCallback(() => {
|
||||
Object.entries(themes[theme]).forEach(([key, value]) => {
|
||||
document.documentElement.style.setProperty(`--${key}`, value);
|
||||
});
|
||||
}, [theme]);
|
||||
|
||||
// On Component Mount and Component Update
|
||||
useEffect(() => {
|
||||
updateCSSVariables();
|
||||
}, [updateCSSVariables, theme]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export default ApplyTheme;
|
||||
@@ -28,11 +28,11 @@ function ConnectionLostModal(props) {
|
||||
|
||||
<ModalBody>
|
||||
<div>
|
||||
{translate('ConnectionLostToBackend')}
|
||||
{translate('ConnectionLostToBackend', { appName: 'Radarr' })}
|
||||
</div>
|
||||
|
||||
<div className={styles.automatic}>
|
||||
{translate('ConnectionLostReconnect')}
|
||||
{translate('ConnectionLostReconnect', { appName: 'Radarr' })}
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import SortDirection from 'Helpers/Props/SortDirection';
|
||||
import { FilterBuilderProp } from './AppState';
|
||||
|
||||
export interface Error {
|
||||
responseJSON: {
|
||||
@@ -18,14 +17,7 @@ export interface AppSectionSaveState {
|
||||
}
|
||||
|
||||
export interface PagedAppSectionState {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
totalPages: number;
|
||||
totalRecords?: number;
|
||||
}
|
||||
|
||||
export interface AppSectionFilterState<T> {
|
||||
filterBuilderProps: FilterBuilderProp<T>[];
|
||||
}
|
||||
|
||||
export interface AppSectionSchemaState<T> {
|
||||
@@ -41,7 +33,6 @@ export interface AppSectionItemState<T> {
|
||||
isFetching: boolean;
|
||||
isPopulated: boolean;
|
||||
error: Error;
|
||||
pendingChanges: Partial<T>;
|
||||
item: T;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import InteractiveImportAppState from 'App/State/InteractiveImportAppState';
|
||||
import BlocklistAppState from './BlocklistAppState';
|
||||
import CalendarAppState from './CalendarAppState';
|
||||
import CommandAppState from './CommandAppState';
|
||||
import HistoryAppState from './HistoryAppState';
|
||||
import MovieCollectionAppState from './MovieCollectionAppState';
|
||||
import MovieFilesAppState from './MovieFilesAppState';
|
||||
import MoviesAppState, { MovieIndexAppState } from './MoviesAppState';
|
||||
@@ -45,20 +43,9 @@ export interface CustomFilter {
|
||||
filers: PropertyFilter[];
|
||||
}
|
||||
|
||||
export interface AppSectionState {
|
||||
dimensions: {
|
||||
isSmallScreen: boolean;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface AppState {
|
||||
app: AppSectionState;
|
||||
blocklist: BlocklistAppState;
|
||||
calendar: CalendarAppState;
|
||||
commands: CommandAppState;
|
||||
history: HistoryAppState;
|
||||
interactiveImport: InteractiveImportAppState;
|
||||
movieCollections: MovieCollectionAppState;
|
||||
movieFiles: MovieFilesAppState;
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import Blocklist from 'typings/Blocklist';
|
||||
import AppSectionState, { AppSectionFilterState } from './AppSectionState';
|
||||
|
||||
interface BlocklistAppState
|
||||
extends AppSectionState<Blocklist>,
|
||||
AppSectionFilterState<Blocklist> {}
|
||||
|
||||
export default BlocklistAppState;
|
||||
@@ -1,10 +1,9 @@
|
||||
import AppSectionState, {
|
||||
AppSectionFilterState,
|
||||
} from 'App/State/AppSectionState';
|
||||
import AppSectionState from 'App/State/AppSectionState';
|
||||
import Movie from 'Movie/Movie';
|
||||
import { FilterBuilderProp } from './AppState';
|
||||
|
||||
interface CalendarAppState
|
||||
extends AppSectionState<Movie>,
|
||||
AppSectionFilterState<Movie> {}
|
||||
interface CalendarAppState extends AppSectionState<Movie> {
|
||||
filterBuilderProps: FilterBuilderProp<Movie>[];
|
||||
}
|
||||
|
||||
export default CalendarAppState;
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import AppSectionState, {
|
||||
AppSectionFilterState,
|
||||
} from 'App/State/AppSectionState';
|
||||
import History from 'typings/History';
|
||||
|
||||
interface HistoryAppState
|
||||
extends AppSectionState<History>,
|
||||
AppSectionFilterState<History> {}
|
||||
|
||||
export default HistoryAppState;
|
||||
@@ -1,8 +1,6 @@
|
||||
import AppSectionState from 'App/State/AppSectionState';
|
||||
import MovieCollection from 'typings/MovieCollection';
|
||||
|
||||
interface MovieCollectionAppState extends AppSectionState<MovieCollection> {
|
||||
itemMap: Record<number, number>;
|
||||
}
|
||||
type MovieCollectionAppState = AppSectionState<MovieCollection>;
|
||||
|
||||
export default MovieCollectionAppState;
|
||||
|
||||
@@ -25,7 +25,6 @@ export interface MovieIndexAppState {
|
||||
showTmdbRating: boolean;
|
||||
showImdbRating: boolean;
|
||||
showRottenTomatoesRating: boolean;
|
||||
showTags: boolean;
|
||||
showSearchAction: boolean;
|
||||
};
|
||||
|
||||
@@ -38,7 +37,6 @@ export interface MovieIndexAppState {
|
||||
showAdded: boolean;
|
||||
showPath: boolean;
|
||||
showSizeOnDisk: boolean;
|
||||
showTags: boolean;
|
||||
showSearchAction: boolean;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,17 +1,41 @@
|
||||
import Queue from 'typings/Queue';
|
||||
import AppSectionState, {
|
||||
AppSectionFilterState,
|
||||
AppSectionItemState,
|
||||
Error,
|
||||
} from './AppSectionState';
|
||||
import ModelBase from 'App/ModelBase';
|
||||
import Language from 'Language/Language';
|
||||
import { QualityModel } from 'Quality/Quality';
|
||||
import CustomFormat from 'typings/CustomFormat';
|
||||
import AppSectionState, { AppSectionItemState, Error } from './AppSectionState';
|
||||
|
||||
export interface StatusMessage {
|
||||
title: string;
|
||||
messages: string[];
|
||||
}
|
||||
|
||||
export interface Queue extends ModelBase {
|
||||
languages: Language[];
|
||||
quality: QualityModel;
|
||||
customFormats: CustomFormat[];
|
||||
size: number;
|
||||
title: string;
|
||||
sizeleft: number;
|
||||
timeleft: string;
|
||||
estimatedCompletionTime: string;
|
||||
status: string;
|
||||
trackedDownloadStatus: string;
|
||||
trackedDownloadState: string;
|
||||
statusMessages: StatusMessage[];
|
||||
errorMessage: string;
|
||||
downloadId: string;
|
||||
protocol: string;
|
||||
downloadClient: string;
|
||||
outputPath: string;
|
||||
movieHasFile: boolean;
|
||||
movieId?: number;
|
||||
}
|
||||
|
||||
export interface QueueDetailsAppState extends AppSectionState<Queue> {
|
||||
params: unknown;
|
||||
}
|
||||
|
||||
export interface QueuePagedAppState
|
||||
extends AppSectionState<Queue>,
|
||||
AppSectionFilterState<Queue> {
|
||||
export interface QueuePagedAppState extends AppSectionState<Queue> {
|
||||
isGrabbing: boolean;
|
||||
grabError: Error;
|
||||
isRemoving: boolean;
|
||||
|
||||
@@ -3,13 +3,10 @@ import AppSectionState, {
|
||||
AppSectionItemState,
|
||||
AppSectionSaveState,
|
||||
AppSectionSchemaState,
|
||||
PagedAppSectionState,
|
||||
} from 'App/State/AppSectionState';
|
||||
import Language from 'Language/Language';
|
||||
import DownloadClient from 'typings/DownloadClient';
|
||||
import ImportList from 'typings/ImportList';
|
||||
import ImportListExclusion from 'typings/ImportListExclusion';
|
||||
import ImportListOptionsSettings from 'typings/ImportListOptionsSettings';
|
||||
import Indexer from 'typings/Indexer';
|
||||
import IndexerFlag from 'typings/IndexerFlag';
|
||||
import Notification from 'typings/Notification';
|
||||
@@ -39,27 +36,12 @@ export interface QualityProfilesAppState
|
||||
extends AppSectionState<QualityProfile>,
|
||||
AppSectionSchemaState<QualityProfile> {}
|
||||
|
||||
export interface ImportListOptionsSettingsAppState
|
||||
extends AppSectionItemState<ImportListOptionsSettings>,
|
||||
AppSectionSaveState {}
|
||||
|
||||
export interface ImportListExclusionsSettingsAppState
|
||||
extends AppSectionState<ImportListExclusion>,
|
||||
AppSectionSaveState,
|
||||
PagedAppSectionState,
|
||||
AppSectionDeleteState {
|
||||
pendingChanges: Partial<ImportListExclusion>;
|
||||
}
|
||||
|
||||
export type IndexerFlagSettingsAppState = AppSectionState<IndexerFlag>;
|
||||
export type LanguageSettingsAppState = AppSectionState<Language>;
|
||||
export type UiSettingsAppState = AppSectionItemState<UiSettings>;
|
||||
|
||||
interface SettingsAppState {
|
||||
advancedSettings: boolean;
|
||||
downloadClients: DownloadClientAppState;
|
||||
importListExclusions: ImportListExclusionsSettingsAppState;
|
||||
importListOptions: ImportListOptionsSettingsAppState;
|
||||
importLists: ImportListAppState;
|
||||
indexerFlags: IndexerFlagSettingsAppState;
|
||||
indexers: IndexerAppState;
|
||||
|
||||
@@ -42,9 +42,9 @@ function Agenda(props) {
|
||||
<div className={styles.agenda}>
|
||||
{
|
||||
items.map((item, index) => {
|
||||
const momentDate = moment(item.sortDate);
|
||||
const momentDate = moment(item.inCinemas);
|
||||
const showDate = index === 0 ||
|
||||
!moment(items[index - 1].sortDate).isSame(momentDate, 'day');
|
||||
!moment(items[index - 1].inCinemas).isSame(momentDate, 'day');
|
||||
|
||||
return (
|
||||
<AgendaEventConnector
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointSmall) {
|
||||
.overlay {
|
||||
.event {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@@ -111,4 +111,5 @@
|
||||
.releaseIcon {
|
||||
margin-right: 20px;
|
||||
width: 25px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ import moment from 'moment';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import CalendarEventQueueDetails from 'Calendar/Events/CalendarEventQueueDetails';
|
||||
import getStatusStyle from 'Calendar/getStatusStyle';
|
||||
import Icon from 'Components/Icon';
|
||||
import Link from 'Components/Link/Link';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import getStatusStyle from 'Utilities/Movie/getStatusStyle';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './AgendaEvent.css';
|
||||
|
||||
@@ -82,7 +82,7 @@ class AgendaEvent extends Component {
|
||||
startTime = moment(startTime);
|
||||
const downloading = !!(queueItem || grabbed);
|
||||
const isMonitored = monitored;
|
||||
const statusStyle = getStatusStyle(hasFile, downloading, isMonitored, isAvailable);
|
||||
const statusStyle = getStatusStyle(null, isMonitored, hasFile, isAvailable, 'style', downloading);
|
||||
const joinedGenres = genres.slice(0, 2).join(', ');
|
||||
const link = `/movie/${titleSlug}`;
|
||||
|
||||
@@ -95,7 +95,7 @@ class AgendaEvent extends Component {
|
||||
|
||||
<div className={styles.overlay}>
|
||||
<div className={styles.date}>
|
||||
{showDate ? startTime.format(longDateFormat) : null}
|
||||
{(showDate) ? startTime.format(longDateFormat) : null}
|
||||
</div>
|
||||
|
||||
<div className={styles.releaseIcon}>
|
||||
@@ -147,7 +147,7 @@ class AgendaEvent extends Component {
|
||||
className={styles.statusIcon}
|
||||
name={icons.MOVIE_FILE}
|
||||
kind={kinds.WARNING}
|
||||
title={translate('QualityCutoffNotMet')}
|
||||
title={translate('QualityCutoffHasNotBeenMet')}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -33,7 +33,9 @@ class Calendar extends Component {
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<Alert kind={kinds.DANGER}>{translate('CalendarLoadError')}</Alert>
|
||||
<Alert kind={kinds.DANGER}>
|
||||
{translate('UnableToLoadTheCalendar')}
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -55,7 +55,7 @@ class CalendarConnector extends Component {
|
||||
gotoCalendarToday
|
||||
} = this.props;
|
||||
|
||||
registerPagePopulator(this.repopulate, ['movieFileUpdated', 'movieFileDeleted']);
|
||||
registerPagePopulator(this.repopulate);
|
||||
|
||||
if (useCurrentPage) {
|
||||
fetchCalendar();
|
||||
|
||||
@@ -23,11 +23,13 @@ function createFilterBuilderPropsSelector() {
|
||||
);
|
||||
}
|
||||
|
||||
interface CalendarFilterModalProps {
|
||||
interface SeriesIndexFilterModalProps {
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
export default function CalendarFilterModal(props: CalendarFilterModalProps) {
|
||||
export default function CalendarFilterModal(
|
||||
props: SeriesIndexFilterModalProps
|
||||
) {
|
||||
const sectionItems = useSelector(createCalendarSelector());
|
||||
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
|
||||
const customFilterType = 'calendar';
|
||||
|
||||
@@ -104,7 +104,7 @@ class CalendarPage extends Component {
|
||||
<PageToolbar>
|
||||
<PageToolbarSection>
|
||||
<PageToolbarButton
|
||||
label={translate('ICalLink')}
|
||||
label={translate('iCalLink')}
|
||||
iconName={icons.CALENDAR}
|
||||
onPress={this.onGetCalendarLinkPress}
|
||||
/>
|
||||
@@ -112,7 +112,7 @@ class CalendarPage extends Component {
|
||||
<PageToolbarSeparator />
|
||||
|
||||
<PageToolbarButton
|
||||
label={translate('RssSync')}
|
||||
label={translate('RSSSync')}
|
||||
iconName={icons.RSS}
|
||||
isSpinning={isRssSyncExecuting}
|
||||
onPress={onRssSyncPress}
|
||||
@@ -180,7 +180,7 @@ class CalendarPage extends Component {
|
||||
|
||||
{
|
||||
!movieError && movieIsPopulated && !hasMovie &&
|
||||
<NoMovie totalItems={0} />
|
||||
<NoMovie />
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -48,10 +48,6 @@ $fullColorGradient: rgba(244, 245, 246, 0.2);
|
||||
.statusContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:global(.fullColor) {
|
||||
filter: var(--calendarFullColorFilter)
|
||||
}
|
||||
}
|
||||
|
||||
.statusIcon {
|
||||
|
||||
@@ -2,10 +2,10 @@ import classNames from 'classnames';
|
||||
import moment from 'moment';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import getStatusStyle from 'Calendar/getStatusStyle';
|
||||
import Icon from 'Components/Icon';
|
||||
import Link from 'Components/Link/Link';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import getStatusStyle from 'Utilities/Movie/getStatusStyle';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import CalendarEventQueueDetails from './CalendarEventQueueDetails';
|
||||
import styles from './CalendarEvent.css';
|
||||
@@ -39,7 +39,7 @@ class CalendarEvent extends Component {
|
||||
|
||||
const isDownloading = !!(queueItem || grabbed);
|
||||
const isMonitored = monitored;
|
||||
const statusStyle = getStatusStyle(hasFile, isDownloading, isMonitored, isAvailable);
|
||||
const statusStyle = getStatusStyle(null, isMonitored, hasFile, isAvailable, 'style', isDownloading);
|
||||
const joinedGenres = genres.slice(0, 2).join(', ');
|
||||
const link = `/movie/${titleSlug}`;
|
||||
const eventType = [];
|
||||
@@ -76,18 +76,12 @@ class CalendarEvent extends Component {
|
||||
{title}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={classNames(
|
||||
styles.statusContainer,
|
||||
fullColorEvents && 'fullColor'
|
||||
)}
|
||||
>
|
||||
<div className={styles.statusContainer}>
|
||||
{
|
||||
queueItem ?
|
||||
<span className={styles.statusIcon}>
|
||||
<CalendarEventQueueDetails
|
||||
{...queueItem}
|
||||
fullColorEvents={fullColorEvents}
|
||||
/>
|
||||
</span> :
|
||||
null
|
||||
@@ -104,14 +98,12 @@ class CalendarEvent extends Component {
|
||||
}
|
||||
|
||||
{
|
||||
showCutoffUnmetIcon &&
|
||||
!!movieFile &&
|
||||
movieFile.qualityCutoffNotMet ?
|
||||
showCutoffUnmetIcon && !!movieFile && movieFile.qualityCutoffNotMet ?
|
||||
<Icon
|
||||
className={styles.statusIcon}
|
||||
name={icons.MOVIE_FILE}
|
||||
kind={kinds.WARNING}
|
||||
title={translate('QualityCutoffNotMet')}
|
||||
title={translate('QualityCutoffHasNotBeenMet')}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ class CalendarHeader extends Component {
|
||||
isDisabled={view === calendarViews.AGENDA}
|
||||
onPress={onTodayPress}
|
||||
>
|
||||
{translate('Today')}
|
||||
Today
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -20,11 +20,10 @@ function Legend(props) {
|
||||
if (showCutoffUnmetIcon) {
|
||||
iconsToShow.push(
|
||||
<LegendIconItem
|
||||
name={translate('CutoffNotMet')}
|
||||
name={translate('CutoffUnmet')}
|
||||
icon={icons.MOVIE_FILE}
|
||||
kind={kinds.WARNING}
|
||||
fullColorEvents={fullColorEvents}
|
||||
tooltip={translate('QualityCutoffNotMet')}
|
||||
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
|
||||
tooltip={translate('QualityOrLangCutoffHasNotBeenMet')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,8 +4,4 @@
|
||||
|
||||
.icon {
|
||||
margin-right: 5px;
|
||||
|
||||
&:global(.fullColorEvents) {
|
||||
filter: var(--calendarFullColorFilter)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
@@ -7,9 +6,9 @@ import styles from './LegendIconItem.css';
|
||||
function LegendIconItem(props) {
|
||||
const {
|
||||
name,
|
||||
fullColorEvents,
|
||||
icon,
|
||||
kind,
|
||||
darken,
|
||||
tooltip
|
||||
} = props;
|
||||
|
||||
@@ -19,11 +18,9 @@ function LegendIconItem(props) {
|
||||
title={tooltip}
|
||||
>
|
||||
<Icon
|
||||
className={classNames(
|
||||
styles.icon,
|
||||
fullColorEvents && 'fullColorEvents'
|
||||
)}
|
||||
className={styles.icon}
|
||||
name={icon}
|
||||
darken={darken}
|
||||
kind={kind}
|
||||
/>
|
||||
|
||||
@@ -34,10 +31,14 @@ function LegendIconItem(props) {
|
||||
|
||||
LegendIconItem.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
fullColorEvents: PropTypes.bool.isRequired,
|
||||
icon: PropTypes.object.isRequired,
|
||||
kind: PropTypes.string.isRequired,
|
||||
darken: PropTypes.bool.isRequired,
|
||||
tooltip: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
LegendIconItem.defaultProps = {
|
||||
darken: false
|
||||
};
|
||||
|
||||
export default LegendIconItem;
|
||||
|
||||
@@ -135,7 +135,7 @@ class CalendarOptionsModalContent extends Component {
|
||||
type={inputTypes.CHECK}
|
||||
name="showCutoffUnmetIcon"
|
||||
value={showCutoffUnmetIcon}
|
||||
helpText={translate('IconForCutoffUnmetHelpText')}
|
||||
helpText={translate('ShowCutoffUnmetIconHelpText')}
|
||||
onChange={this.onOptionInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
@@ -177,7 +177,7 @@ class CalendarOptionsModalContent extends Component {
|
||||
values={weekColumnOptions}
|
||||
value={calendarWeekColumnHeader}
|
||||
onChange={this.onGlobalInputChange}
|
||||
helpText={translate('WeekColumnHeaderHelpText')}
|
||||
helpText={translate('SettingsWeekColumnHeaderHelpText')}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
function getStatusStyle(hasFile, downloading, isMonitored, isAvailable) {
|
||||
if (downloading) {
|
||||
return 'queue';
|
||||
}
|
||||
|
||||
if (hasFile && isMonitored) {
|
||||
return 'downloaded';
|
||||
}
|
||||
|
||||
if (hasFile && !isMonitored) {
|
||||
return 'unmonitored';
|
||||
}
|
||||
|
||||
if (isAvailable && isMonitored) {
|
||||
return 'missingMonitored';
|
||||
}
|
||||
|
||||
if (!isMonitored) {
|
||||
return 'missingUnmonitored';
|
||||
}
|
||||
|
||||
return 'continuing';
|
||||
}
|
||||
|
||||
export default getStatusStyle;
|
||||
@@ -109,7 +109,7 @@ class CalendarLinkModalContent extends Component {
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{translate('CalendarFeed')}
|
||||
{translate('RadarrCalendarFeed')}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
@@ -121,19 +121,19 @@ class CalendarLinkModalContent extends Component {
|
||||
type={inputTypes.CHECK}
|
||||
name="unmonitored"
|
||||
value={unmonitored}
|
||||
helpText={translate('ICalIncludeUnmonitoredMoviesHelpText')}
|
||||
helpText={translate('UnmonitoredHelpText')}
|
||||
onChange={this.onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('ICalShowAsAllDayEvents')}</FormLabel>
|
||||
<FormLabel>{translate('ShowAsAllDayEvents')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="asAllDay"
|
||||
value={asAllDay}
|
||||
helpText={translate('ICalShowAsAllDayEventsHelpText')}
|
||||
helpText={translate('AsAllDayHelpText')}
|
||||
onChange={this.onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
@@ -145,7 +145,7 @@ class CalendarLinkModalContent extends Component {
|
||||
type={inputTypes.TAG}
|
||||
name="tags"
|
||||
value={tags}
|
||||
helpText={translate('ICalTagsMoviesHelpText')}
|
||||
helpText={translate('TagsHelpText')}
|
||||
onChange={this.onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
@@ -160,7 +160,7 @@ class CalendarLinkModalContent extends Component {
|
||||
name="iCalHttpUrl"
|
||||
value={iCalHttpUrl}
|
||||
readOnly={true}
|
||||
helpText={translate('ICalFeedHelpText')}
|
||||
helpText={translate('ICalHttpUrlHelpText')}
|
||||
buttons={[
|
||||
<ClipboardButton
|
||||
key="copy"
|
||||
|
||||
@@ -6,7 +6,6 @@ import * as commandNames from 'Commands/commandNames';
|
||||
import withScrollPosition from 'Components/withScrollPosition';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import { saveMovieCollections, setMovieCollectionsFilter, setMovieCollectionsSort } from 'Store/Actions/movieCollectionActions';
|
||||
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
|
||||
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||
import scrollPositions from 'Store/scrollPositions';
|
||||
import createCollectionClientSideCollectionItemsSelector from 'Store/Selectors/createCollectionClientSideCollectionItemsSelector';
|
||||
@@ -39,12 +38,6 @@ function createMapDispatchToProps(dispatch, props) {
|
||||
dispatchFetchRootFolders() {
|
||||
dispatch(fetchRootFolders());
|
||||
},
|
||||
dispatchFetchQueueDetails() {
|
||||
dispatch(fetchQueueDetails());
|
||||
},
|
||||
dispatchClearQueueDetails() {
|
||||
dispatch(clearQueueDetails());
|
||||
},
|
||||
onUpdateSelectedPress(payload) {
|
||||
dispatch(saveMovieCollections(payload));
|
||||
},
|
||||
@@ -70,12 +63,10 @@ class CollectionConnector extends Component {
|
||||
componentDidMount() {
|
||||
registerPagePopulator(this.repopulate);
|
||||
this.props.dispatchFetchRootFolders();
|
||||
this.props.dispatchFetchQueueDetails();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
unregisterPagePopulator(this.repopulate);
|
||||
this.props.dispatchClearQueueDetails();
|
||||
}
|
||||
|
||||
//
|
||||
@@ -108,9 +99,7 @@ CollectionConnector.propTypes = {
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
view: PropTypes.string.isRequired,
|
||||
onUpdateSelectedPress: PropTypes.func.isRequired,
|
||||
dispatchFetchRootFolders: PropTypes.func.isRequired,
|
||||
dispatchFetchQueueDetails: PropTypes.func.isRequired,
|
||||
dispatchClearQueueDetails: PropTypes.func.isRequired
|
||||
dispatchFetchRootFolders: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default withScrollPosition(
|
||||
|
||||
@@ -14,50 +14,6 @@ import styles from './CollectionFooter.css';
|
||||
|
||||
const NO_CHANGE = 'noChange';
|
||||
|
||||
const monitoredOptions = [
|
||||
{
|
||||
key: NO_CHANGE,
|
||||
get value() {
|
||||
return translate('NoChange');
|
||||
},
|
||||
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 {
|
||||
|
||||
//
|
||||
@@ -67,12 +23,12 @@ class CollectionFooter extends Component {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
monitored: NO_CHANGE,
|
||||
monitor: NO_CHANGE,
|
||||
monitored: NO_CHANGE,
|
||||
qualityProfileId: NO_CHANGE,
|
||||
minimumAvailability: NO_CHANGE,
|
||||
rootFolderPath: NO_CHANGE,
|
||||
searchOnAdd: NO_CHANGE
|
||||
destinationRootFolder: null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -88,9 +44,8 @@ class CollectionFooter extends Component {
|
||||
monitored: NO_CHANGE,
|
||||
monitor: NO_CHANGE,
|
||||
qualityProfileId: NO_CHANGE,
|
||||
minimumAvailability: NO_CHANGE,
|
||||
rootFolderPath: NO_CHANGE,
|
||||
searchOnAdd: NO_CHANGE
|
||||
minimumAvailability: NO_CHANGE
|
||||
});
|
||||
}
|
||||
|
||||
@@ -108,12 +63,11 @@ class CollectionFooter extends Component {
|
||||
|
||||
onUpdateSelectedPress = () => {
|
||||
const {
|
||||
monitored,
|
||||
monitor,
|
||||
monitored,
|
||||
qualityProfileId,
|
||||
minimumAvailability,
|
||||
rootFolderPath,
|
||||
searchOnAdd
|
||||
rootFolderPath
|
||||
} = this.state;
|
||||
|
||||
const changes = {};
|
||||
@@ -138,10 +92,6 @@ class CollectionFooter extends Component {
|
||||
changes.rootFolderPath = rootFolderPath;
|
||||
}
|
||||
|
||||
if (searchOnAdd !== NO_CHANGE) {
|
||||
changes.searchOnAdd = searchOnAdd === 'yes';
|
||||
}
|
||||
|
||||
this.props.onUpdateSelectedPress(changes);
|
||||
};
|
||||
|
||||
@@ -159,10 +109,15 @@ class CollectionFooter extends Component {
|
||||
monitor,
|
||||
qualityProfileId,
|
||||
minimumAvailability,
|
||||
rootFolderPath,
|
||||
searchOnAdd
|
||||
rootFolderPath
|
||||
} = this.state;
|
||||
|
||||
const monitoredOptions = [
|
||||
{ key: NO_CHANGE, value: translate('NoChange'), disabled: true },
|
||||
{ key: 'monitored', value: translate('Monitored') },
|
||||
{ key: 'unmonitored', value: translate('Unmonitored') }
|
||||
];
|
||||
|
||||
const selectedCount = selectedIds.length;
|
||||
|
||||
return (
|
||||
@@ -170,7 +125,7 @@ class CollectionFooter extends Component {
|
||||
<div className={styles.inputContainer}>
|
||||
<CollectionFooterLabel
|
||||
label={translate('MonitorCollection')}
|
||||
isSaving={isSaving && monitored !== NO_CHANGE}
|
||||
isSaving={isSaving}
|
||||
/>
|
||||
|
||||
<SelectInput
|
||||
@@ -185,7 +140,7 @@ class CollectionFooter extends Component {
|
||||
<div className={styles.inputContainer}>
|
||||
<CollectionFooterLabel
|
||||
label={translate('MonitorMovies')}
|
||||
isSaving={isSaving && monitor !== NO_CHANGE}
|
||||
isSaving={isSaving}
|
||||
/>
|
||||
|
||||
<SelectInput
|
||||
@@ -243,25 +198,10 @@ class CollectionFooter extends Component {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.inputContainer}>
|
||||
<CollectionFooterLabel
|
||||
label={translate('SearchMoviesOnAdd')}
|
||||
isSaving={isSaving && searchOnAdd !== NO_CHANGE}
|
||||
/>
|
||||
|
||||
<SelectInput
|
||||
name="searchOnAdd"
|
||||
value={searchOnAdd}
|
||||
values={searchOnAddOptions}
|
||||
isDisabled={!selectedCount}
|
||||
onChange={this.onInputChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.buttonContainer}>
|
||||
<div className={styles.buttonContainerContent}>
|
||||
<CollectionFooterLabel
|
||||
label={translate('CountCollectionsSelected', { count: selectedCount })}
|
||||
label={translate('CollectionsSelectedInterp', [selectedCount])}
|
||||
isSaving={false}
|
||||
/>
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ function createMapStateToProps() {
|
||||
|
||||
return {
|
||||
...collection,
|
||||
movies: [...collection.movies].sort((a, b) => b.year - a.year),
|
||||
genres: Array.from(new Set(allGenres)).slice(0, 3)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -115,16 +115,3 @@ $hoverScale: 1.05;
|
||||
color: var(--iconButtonHoverLightColor);
|
||||
}
|
||||
}
|
||||
|
||||
.excluded {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-width: 0 25px 25px 0;
|
||||
border-style: solid;
|
||||
border-color: transparent var(--dangerColor) transparent transparent;
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ interface CssExports {
|
||||
'content': string;
|
||||
'controls': string;
|
||||
'editorSelect': string;
|
||||
'excluded': string;
|
||||
'externalLinks': string;
|
||||
'link': string;
|
||||
'monitorToggleButton': string;
|
||||
|
||||
@@ -5,7 +5,6 @@ import MonitorToggleButton from 'Components/MonitorToggleButton';
|
||||
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
||||
import MovieIndexProgressBar from 'Movie/Index/ProgressBar/MovieIndexProgressBar';
|
||||
import MoviePoster from 'Movie/MoviePoster';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import AddNewCollectionMovieModal from './../AddNewCollectionMovieModal';
|
||||
import styles from './CollectionMovie.css';
|
||||
|
||||
@@ -62,7 +61,6 @@ class CollectionMovie extends Component {
|
||||
const {
|
||||
id,
|
||||
title,
|
||||
status,
|
||||
overview,
|
||||
year,
|
||||
tmdbId,
|
||||
@@ -71,9 +69,7 @@ class CollectionMovie extends Component {
|
||||
hasFile,
|
||||
folder,
|
||||
isAvailable,
|
||||
movieFile,
|
||||
isExistingMovie,
|
||||
isExcluded,
|
||||
posterWidth,
|
||||
posterHeight,
|
||||
detailedProgressBar,
|
||||
@@ -109,15 +105,6 @@ class CollectionMovie extends Component {
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
isExcluded ?
|
||||
<div
|
||||
className={styles.excluded}
|
||||
title={translate('Excluded')}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
<Link
|
||||
className={styles.link}
|
||||
style={elementStyle}
|
||||
@@ -136,15 +123,13 @@ class CollectionMovie extends Component {
|
||||
|
||||
<div className={styles.overlay}>
|
||||
<div className={styles.overlayTitle}>
|
||||
{title} {year > 0 ? `(${year})` : ''}
|
||||
{title}
|
||||
</div>
|
||||
|
||||
{
|
||||
id ?
|
||||
id &&
|
||||
<div className={styles.overlayStatus}>
|
||||
<MovieIndexProgressBar
|
||||
movieId={id}
|
||||
movieFile={movieFile}
|
||||
monitored={monitored}
|
||||
hasFile={hasFile}
|
||||
status={status}
|
||||
@@ -153,8 +138,7 @@ class CollectionMovie extends Component {
|
||||
detailedProgressBar={detailedProgressBar}
|
||||
isAvailable={isAvailable}
|
||||
/>
|
||||
</div> :
|
||||
null
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</Link>
|
||||
@@ -187,20 +171,17 @@ CollectionMovie.propTypes = {
|
||||
id: PropTypes.number,
|
||||
title: PropTypes.string.isRequired,
|
||||
year: PropTypes.number.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
overview: PropTypes.string.isRequired,
|
||||
monitored: PropTypes.bool,
|
||||
collectionId: PropTypes.number.isRequired,
|
||||
hasFile: PropTypes.bool,
|
||||
folder: PropTypes.string,
|
||||
isAvailable: PropTypes.bool,
|
||||
movieFile: PropTypes.object,
|
||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
posterWidth: PropTypes.number.isRequired,
|
||||
posterHeight: PropTypes.number.isRequired,
|
||||
detailedProgressBar: PropTypes.bool.isRequired,
|
||||
isExistingMovie: PropTypes.bool,
|
||||
isExcluded: PropTypes.bool,
|
||||
tmdbId: PropTypes.number.isRequired,
|
||||
imdbId: PropTypes.string,
|
||||
youTubeTrailerId: PropTypes.string,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
margin: 2px 4px;
|
||||
border: 1px solid var(--borderColor);
|
||||
border-radius: 4px;
|
||||
background-color: var(--inputBackgroundColor);
|
||||
background-color: #eee;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
padding: 0 4px;
|
||||
border-left: 4px;
|
||||
border-left-style: solid;
|
||||
background-color: var(--themeLightColor);
|
||||
background-color: var(--white);
|
||||
color: var(--defaultColor);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
||||
import getProgressBarKind from 'Utilities/Movie/getProgressBarKind';
|
||||
import getStatusStyle from 'Utilities/Movie/getStatusStyle';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './CollectionMovieLabel.css';
|
||||
|
||||
@@ -14,7 +14,6 @@ class CollectionMovieLabel extends Component {
|
||||
const {
|
||||
id,
|
||||
title,
|
||||
year,
|
||||
status,
|
||||
monitored,
|
||||
isAvailable,
|
||||
@@ -36,7 +35,9 @@ class CollectionMovieLabel extends Component {
|
||||
}
|
||||
|
||||
<span>
|
||||
{title} {year > 0 ? `(${year})` : ''}
|
||||
{
|
||||
title
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -45,7 +46,7 @@ class CollectionMovieLabel extends Component {
|
||||
<div
|
||||
className={classNames(
|
||||
styles.movieStatus,
|
||||
styles[getProgressBarKind(status, monitored, hasFile, isAvailable)]
|
||||
styles[getStatusStyle(status, monitored, hasFile, isAvailable, 'kinds')]
|
||||
)}
|
||||
>
|
||||
{
|
||||
@@ -61,7 +62,6 @@ class CollectionMovieLabel extends Component {
|
||||
CollectionMovieLabel.propTypes = {
|
||||
id: PropTypes.number,
|
||||
title: PropTypes.string.isRequired,
|
||||
year: PropTypes.number.isRequired,
|
||||
status: PropTypes.string,
|
||||
isAvailable: PropTypes.bool,
|
||||
monitored: PropTypes.bool,
|
||||
@@ -74,7 +74,11 @@ CollectionMovieLabel.propTypes = {
|
||||
|
||||
CollectionMovieLabel.defaultProps = {
|
||||
isSaving: false,
|
||||
statistics: {}
|
||||
statistics: {
|
||||
episodeFileCount: 0,
|
||||
totalEpisodeCount: 0,
|
||||
percentOfEpisodes: 0
|
||||
}
|
||||
};
|
||||
|
||||
export default CollectionMovieLabel;
|
||||
|
||||
@@ -28,6 +28,7 @@ function calculatePosterWidth(posterSize, isSmallScreen) {
|
||||
}
|
||||
|
||||
function calculateRowHeight(posterHeight, sortKey, isSmallScreen, overviewOptions) {
|
||||
|
||||
const heights = [
|
||||
overviewOptions.showPosters ? posterHeight : 75,
|
||||
isSmallScreen ? columnPaddingSmallScreen : columnPadding
|
||||
@@ -121,8 +122,8 @@ class CollectionOverviews extends Component {
|
||||
overviewOptions
|
||||
} = this.props;
|
||||
|
||||
const posterWidth = overviewOptions.showPosters ? calculatePosterWidth(overviewOptions.size, isSmallScreen) : 0;
|
||||
const posterHeight = overviewOptions.showPosters ? calculatePosterHeight(posterWidth) : 0;
|
||||
const posterWidth = calculatePosterWidth(overviewOptions.size, isSmallScreen);
|
||||
const posterHeight = calculatePosterHeight(posterWidth);
|
||||
const rowHeight = calculateRowHeight(posterHeight, sortKey, isSmallScreen, overviewOptions);
|
||||
|
||||
this.setState({
|
||||
|
||||
@@ -13,7 +13,6 @@ export interface CommandBody {
|
||||
trigger: string;
|
||||
suppressMessages: boolean;
|
||||
movieId?: number;
|
||||
movieIds?: number[];
|
||||
}
|
||||
|
||||
interface Command extends ModelBase {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
.description {
|
||||
line-height: $lineHeight;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-left: 0;
|
||||
line-height: $lineHeight;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
|
||||
@@ -63,7 +63,7 @@ function ErrorBoundaryError(props: ErrorBoundaryErrorProps) {
|
||||
<div>{info.componentStack}</div>
|
||||
)}
|
||||
|
||||
<div className={styles.version}>Version: {window.Radarr.version}</div>
|
||||
{<div className={styles.version}>Version: {window.Radarr.version}</div>}
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { maxBy } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
@@ -51,7 +50,7 @@ class FilterBuilderModalContent extends Component {
|
||||
if (id) {
|
||||
dispatchSetFilter({ selectedFilterKey: id });
|
||||
} else {
|
||||
const last = maxBy(customFilters, 'id');
|
||||
const last = customFilters[customFilters.length -1];
|
||||
dispatchSetFilter({ selectedFilterKey: last.id });
|
||||
}
|
||||
|
||||
@@ -109,7 +108,7 @@ class FilterBuilderModalContent extends Component {
|
||||
this.setState({
|
||||
labelErrors: [
|
||||
{
|
||||
message: translate('LabelIsRequired')
|
||||
message: 'Label is required'
|
||||
}
|
||||
]
|
||||
});
|
||||
@@ -147,13 +146,13 @@ class FilterBuilderModalContent extends Component {
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{translate('CustomFilter')}
|
||||
Custom Filter
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<div className={styles.labelContainer}>
|
||||
<div className={styles.label}>
|
||||
{translate('Label')}
|
||||
Label
|
||||
</div>
|
||||
|
||||
<div className={styles.labelInputContainer}>
|
||||
@@ -167,7 +166,9 @@ class FilterBuilderModalContent extends Component {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.label}>{translate('Filters')}</div>
|
||||
<div className={styles.label}>
|
||||
{translate('Filters')}
|
||||
</div>
|
||||
|
||||
<div className={styles.rows}>
|
||||
{
|
||||
|
||||
@@ -3,19 +3,15 @@ import React, { Component } from 'react';
|
||||
import SelectInput from 'Components/Form/SelectInput';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import { filterBuilderTypes, filterBuilderValueTypes, icons } from 'Helpers/Props';
|
||||
import sortByProp from 'Utilities/Array/sortByProp';
|
||||
import BoolFilterBuilderRowValue from './BoolFilterBuilderRowValue';
|
||||
import DateFilterBuilderRowValue from './DateFilterBuilderRowValue';
|
||||
import FilterBuilderRowValueConnector from './FilterBuilderRowValueConnector';
|
||||
import HistoryEventTypeFilterBuilderRowValue from './HistoryEventTypeFilterBuilderRowValue';
|
||||
import ImportListFilterBuilderRowValueConnector from './ImportListFilterBuilderRowValueConnector';
|
||||
import IndexerFilterBuilderRowValueConnector from './IndexerFilterBuilderRowValueConnector';
|
||||
import LanguageFilterBuilderRowValue from './LanguageFilterBuilderRowValue';
|
||||
import MinimumAvailabilityFilterBuilderRowValue from './MinimumAvailabilityFilterBuilderRowValue';
|
||||
import MovieFilterBuilderRowValue from './MovieFilterBuilderRowValue';
|
||||
import ProtocolFilterBuilderRowValue from './ProtocolFilterBuilderRowValue';
|
||||
import QualityFilterBuilderRowValueConnector from './QualityFilterBuilderRowValueConnector';
|
||||
import QualityProfileFilterBuilderRowValue from './QualityProfileFilterBuilderRowValue';
|
||||
import QualityProfileFilterBuilderRowValueConnector from './QualityProfileFilterBuilderRowValueConnector';
|
||||
import ReleaseStatusFilterBuilderRowValue from './ReleaseStatusFilterBuilderRowValue';
|
||||
import TagFilterBuilderRowValueConnector from './TagFilterBuilderRowValueConnector';
|
||||
import styles from './FilterBuilderRow.css';
|
||||
@@ -62,15 +58,9 @@ function getRowValueConnector(selectedFilterBuilderProp) {
|
||||
case filterBuilderValueTypes.DATE:
|
||||
return DateFilterBuilderRowValue;
|
||||
|
||||
case filterBuilderValueTypes.HISTORY_EVENT_TYPE:
|
||||
return HistoryEventTypeFilterBuilderRowValue;
|
||||
|
||||
case filterBuilderValueTypes.INDEXER:
|
||||
return IndexerFilterBuilderRowValueConnector;
|
||||
|
||||
case filterBuilderValueTypes.LANGUAGE:
|
||||
return LanguageFilterBuilderRowValue;
|
||||
|
||||
case filterBuilderValueTypes.PROTOCOL:
|
||||
return ProtocolFilterBuilderRowValue;
|
||||
|
||||
@@ -78,10 +68,7 @@ function getRowValueConnector(selectedFilterBuilderProp) {
|
||||
return QualityFilterBuilderRowValueConnector;
|
||||
|
||||
case filterBuilderValueTypes.QUALITY_PROFILE:
|
||||
return QualityProfileFilterBuilderRowValue;
|
||||
|
||||
case filterBuilderValueTypes.MOVIE:
|
||||
return MovieFilterBuilderRowValue;
|
||||
return QualityProfileFilterBuilderRowValueConnector;
|
||||
|
||||
case filterBuilderValueTypes.RELEASE_STATUS:
|
||||
return ReleaseStatusFilterBuilderRowValue;
|
||||
@@ -229,7 +216,7 @@ class FilterBuilderRow extends Component {
|
||||
key: name,
|
||||
value: typeof label === 'function' ? label() : label
|
||||
};
|
||||
}).sort(sortByProp('value'));
|
||||
}).sort((a, b) => a.value.localeCompare(b.value));
|
||||
|
||||
const ValueComponent = getRowValueConnector(selectedFilterBuilderProp);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { filterBuilderTypes } from 'Helpers/Props';
|
||||
import * as filterTypes from 'Helpers/Props/filterTypes';
|
||||
import sortByProp from 'Utilities/Array/sortByProp';
|
||||
import sortByName from 'Utilities/Array/sortByName';
|
||||
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
||||
|
||||
function createTagListSelector() {
|
||||
@@ -38,7 +38,7 @@ function createTagListSelector() {
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []).sort(sortByProp('name'));
|
||||
}, []).sort(sortByName);
|
||||
}
|
||||
|
||||
return _.uniqBy(items, 'id');
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { FilterBuilderProp } from 'App/State/AppState';
|
||||
|
||||
interface FilterBuilderRowOnChangeProps {
|
||||
name: string;
|
||||
value: unknown[];
|
||||
}
|
||||
|
||||
interface FilterBuilderRowValueProps {
|
||||
filterType?: string;
|
||||
filterValue: string | number | object | string[] | number[] | object[];
|
||||
selectedFilterBuilderProp: FilterBuilderProp<unknown>;
|
||||
sectionItem: unknown[];
|
||||
onChange: (payload: FilterBuilderRowOnChangeProps) => void;
|
||||
}
|
||||
|
||||
export default FilterBuilderRowValueProps;
|
||||
@@ -1,5 +1,5 @@
|
||||
.tag {
|
||||
display: flex;
|
||||
height: 21px;
|
||||
|
||||
&.isLastTag {
|
||||
.or {
|
||||
@@ -18,5 +18,4 @@
|
||||
.or {
|
||||
margin: 0 3px;
|
||||
color: var(--themeDarkColor);
|
||||
line-height: 31px;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import styles from './FilterBuilderRowValueTag.css';
|
||||
|
||||
function FilterBuilderRowValueTag(props) {
|
||||
return (
|
||||
<div
|
||||
<span
|
||||
className={styles.tag}
|
||||
>
|
||||
<TagInputTag
|
||||
@@ -22,7 +22,7 @@ function FilterBuilderRowValueTag(props) {
|
||||
{translate('Or')}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import React from 'react';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
||||
import FilterBuilderRowValueProps from './FilterBuilderRowValueProps';
|
||||
|
||||
const EVENT_TYPE_OPTIONS = [
|
||||
{
|
||||
id: 1,
|
||||
get name() {
|
||||
return translate('Grabbed');
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
get name() {
|
||||
return translate('Imported');
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
get name() {
|
||||
return translate('Failed');
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
get name() {
|
||||
return translate('Deleted');
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
get name() {
|
||||
return translate('Renamed');
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
get name() {
|
||||
return translate('Ignored');
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
function HistoryEventTypeFilterBuilderRowValue(
|
||||
props: FilterBuilderRowValueProps
|
||||
) {
|
||||
return <FilterBuilderRowValue {...props} tagList={EVENT_TYPE_OPTIONS} />;
|
||||
}
|
||||
|
||||
export default HistoryEventTypeFilterBuilderRowValue;
|
||||
@@ -1,13 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import createLanguagesSelector from 'Store/Selectors/createLanguagesSelector';
|
||||
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
||||
import FilterBuilderRowValueProps from './FilterBuilderRowValueProps';
|
||||
|
||||
function LanguageFilterBuilderRowValue(props: FilterBuilderRowValueProps) {
|
||||
const { items } = useSelector(createLanguagesSelector());
|
||||
|
||||
return <FilterBuilderRowValue {...props} tagList={items} />;
|
||||
}
|
||||
|
||||
export default LanguageFilterBuilderRowValue;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user