Compare commits

..

1 Commits

Author SHA1 Message Date
Mark McDowall
9aacd84390 Fixed: Multiple Downloaded Episodes Scan commands should not run in parallel
(cherry picked from commit b3d1e4f520d14c41aa6a7dff049ee9b9ef48fecb)
2023-03-14 01:12:24 +00:00
425 changed files with 6284 additions and 8160 deletions

View File

@@ -40,9 +40,6 @@ csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = true:suggestion
# Using directive is unnecessary.
dotnet_diagnostic.IDE0005.severity = error
# Stylecop Rules
dotnet_diagnostic.SA0001.severity = none
dotnet_diagnostic.SA1005.severity = none

9
.esprintrc Normal file
View File

@@ -0,0 +1,9 @@
{
"paths": [
"frontend/src/**/*.js"
],
"ignored": [
"**/node_modules/**/*"
],
"port": 5004
}

View File

@@ -73,10 +73,3 @@ body:
Additionally, any additional info? Screenshots? References? Anything that will give us more context about the issue you are encountering!
validations:
required: true
- type: checkboxes
attributes:
label: Trace Logs have been provided as applicable. Reports may be closed if the required logs are not provided.
description: Trace logs are generally required for all bug reports
options:
- label: I have followed the steps in the wiki link above and provided the required trace logs that are relevant and show this issue.
required: true

28
.github/labeler.yml vendored
View File

@@ -1,28 +0,0 @@
'Area: API':
- src/Readarr.Api.V1/**/*
'Area: Db-migration':
- src/NzbDrone.Core/Datastore/Migration/*
'Area: Download Clients':
- src/NzbDrone.Core/Download/Clients/**/*
'Area: Import Lists':
- src/NzbDrone.Core/ImportLists/**/*
'Area: Indexer':
- src/NzbDrone.Core/Indexers/**/*
'Area: Notifications':
- src/NzbDrone.Core/Notifications/**/*
'Area: Organizer':
- src/NzbDrone.Core/Organizer/**/*
'Area: Parser':
- src/NzbDrone.Core/Parser/**/*
'Area: UI':
- frontend/**/*
- package.json
- yarn.lock

View File

@@ -1,12 +0,0 @@
name: "Pull Request Labeler"
on:
- pull_request_target
jobs:
triage:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v4

View File

@@ -9,13 +9,13 @@ jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v4
- uses: dessant/lock-threads@v2
with:
github-token: ${{ github.token }}
issue-inactive-days: '90'
exclude-issue-created-before: ''
exclude-any-issue-labels: ''
add-issue-labels: ''
issue-comment: ''
issue-lock-inactive-days: '90'
issue-exclude-created-before: ''
issue-exclude-labels: ''
issue-lock-labels: ''
issue-lock-comment: ''
issue-lock-reason: 'resolved'
process-only: ''

View File

@@ -8,7 +8,7 @@ jobs:
support:
runs-on: ubuntu-latest
steps:
- uses: dessant/support-requests@v3
- uses: dessant/support-requests@v2
with:
github-token: ${{ github.token }}
support-label: 'Type: Support'
@@ -18,15 +18,4 @@ jobs:
to be a support request. Please hop over onto our [Discord](https://readarr.com/discord)
or [Subreddit](https://reddit.com/r/readarr)
close-issue: true
lock-issue: false
- uses: dessant/support-requests@v3
with:
github-token: ${{ github.token }}
support-label: 'Status: Logs Needed'
issue-comment: >
:wave: @{issue-author}, In order to help you further we'll need to see logs.
You'll need to enable trace logging and replicate the problem that you encountered.
Guidance on how to enable trace logging can be found in
our [troubleshooting guide](https://wiki.servarr.com/readarr/troubleshooting#logging-and-log-files).
close-issue: false
lock-issue: false
lock-issue: false

3
.gitignore vendored
View File

@@ -148,6 +148,3 @@ _temp_*/**/*
## Merge any idea folder
*/**/.idea
*.iml
# API doc generation
.config/

View File

@@ -15,8 +15,7 @@ variables:
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.408'
nodeVersion: '16.X'
dotnetVersion: '6.0.302'
innoVersion: '6.2.0'
windowsImage: 'windows-2022'
linuxImage: 'ubuntu-20.04'
@@ -183,7 +182,7 @@ stages:
- task: NodeTool@0
displayName: Set Node.js version
inputs:
versionSpec: $(nodeVersion)
versionSpec: '12.x'
- checkout: self
submodules: true
fetchDepth: 1
@@ -918,7 +917,7 @@ stages:
- task: NodeTool@0
displayName: Set Node.js version
inputs:
versionSpec: $(nodeVersion)
versionSpec: '12.x'
- checkout: self
submodules: true
fetchDepth: 1
@@ -956,55 +955,6 @@ stages:
cliProjectVersion: '$(readarrVersion)'
cliSources: './frontend'
- task: SonarCloudAnalyze@1
- job: Api_Docs
displayName: API Docs
condition: |
and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop'))
pool:
vmImage: ${{ variables.windowsImage }}
steps:
- task: UseDotNet@2
displayName: 'Install .net core'
inputs:
version: $(dotnetVersion)
- checkout: self
submodules: true
persistCredentials: true
fetchDepth: 1
- bash: ./docs.sh Windows
displayName: Create openapi.json
- bash: |
git config --global user.email "development@lidarr.audio"
git config --global user.name "Servarr"
git checkout -b api-docs
git add .
git status
if git status | grep modified
then
git commit -am 'Automated API Docs update'
git push -f --set-upstream origin api-docs
curl -X POST -H "Authorization: token ${GITHUBTOKEN}" -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/readarr/readarr/pulls -d '{"head":"api-docs","base":"develop","title":"Update API docs"}'
else
echo "No changes since last run"
fi
displayName: Commit API Doc Change
continueOnError: true
env:
GITHUBTOKEN: $(githubToken)
- task: CopyFiles@2
displayName: 'Copy openapi.json to: $(Build.ArtifactStagingDirectory)'
inputs:
SourceFolder: '$(Build.SourcesDirectory)'
Contents: |
**/*openapi.json
TargetFolder: '$(Build.ArtifactStagingDirectory)/api_docs'
- publish: $(Build.ArtifactStagingDirectory)/api_docs
artifact: 'APIDocs'
displayName: Publish API Docs Bundle
condition: and(succeeded(), eq(variables['System.JobAttempt'], '1'))
- job: Analyze_Backend
displayName: Backend

38
docs.sh
View File

@@ -1,38 +0,0 @@
PLATFORM=$1
if [ "$PLATFORM" = "Windows" ]; then
RUNTIME="win-x64"
elif [ "$PLATFORM" = "Linux" ]; then
RUNTIME="linux-x64"
elif [ "$PLATFORM" = "Mac" ]; then
RUNTIME="osx-x64"
else
echo "Platform must be provided as first arguement: Windows, Linux or Mac"
exit 1
fi
outputFolder='_output'
testPackageFolder='_tests'
rm -rf $outputFolder
rm -rf $testPackageFolder
slnFile=src/Readarr.sln
platform=Posix
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.5.0 Swashbuckle.AspNetCore.Cli
dotnet tool run swagger tofile --output ./src/Readarr.Api.V1/openapi.json "$outputFolder/net6.0/$RUNTIME/Readarr.console.dll" v1 &
sleep 45
kill %1
exit 0

25
frontend/.csscomb.json Normal file
View File

@@ -0,0 +1,25 @@
{
"remove-empty-rulesets": true,
"always-semicolon": true,
"color-case": "lower",
"block-indent": " ",
"color-shorthand": false,
"element-case": "lower",
"eof-newline": true,
"leading-zero": true,
"quotes": "double",
"sort-order-fallback": "abc",
"space-before-colon": "",
"space-after-colon": " ",
"space-before-combinator": " ",
"space-after-combinator": " ",
"space-between-declarations": "\n",
"space-before-opening-brace": " ",
"space-after-opening-brace": "\n",
"space-after-selector-delimiter": " ",
"space-before-selector-delimiter": "",
"space-before-closing-brace": "\n",
"strip-spaces": true,
"tab-size": true,
"unitless-zero": false
}

335
frontend/.esformatter Normal file
View File

@@ -0,0 +1,335 @@
{
"indent": {
"value": " ",
"FunctionExpression": 1,
"ArrayExpression": 1,
"ObjectExpression": 1
},
"lineBreak": {
"value": "\n",
"before": {
"ArrayPatternClosing": 0,
"ArrayPatternComma": 0,
"ArrayPatternOpening": 0,
"ArrowFunctionExpressionArrow": 0,
"ArrowFunctionExpressionClosingBrace": ">=1",
"ArrowFunctionExpressionOpeningBrace": 0,
"AssignmentExpression": ">=1",
"AssignmentOperator": 0,
"BlockStatement": 0,
"BreakKeyword": ">=1",
"CallExpression": -1,
"CallExpressionClosingParentheses": -1,
"CallExpressionOpeningParentheses": 0,
"CatchClosingBrace": ">=1",
"CatchKeyword": 0,
"CatchOpeningBrace": 0,
"ClassDeclaration": ">=1",
"ClassDeclarationClosingBrace": ">=1",
"ClassDeclarationOpeningBrace": 0,
"ConditionalExpression": ">=1",
"DeleteOperator": ">=1",
"DoWhileStatement": ">=1",
"DoWhileStatementClosingBrace": ">=1",
"DoWhileStatementOpeningBrace": 0,
"ElseIfStatement": 0,
"ElseIfStatementClosingBrace": ">=1",
"ElseIfStatementOpeningBrace": 0,
"ElseStatement": 0,
"ElseStatementClosingBrace": ">=1",
"ElseStatementOpeningBrace": 0,
"EmptyStatement": -1,
"EndOfFile": -1,
"FinallyClosingBrace": ">=1",
"FinallyKeyword": -1,
"FinallyOpeningBrace": 0,
"ForInStatement": ">=1",
"ForInStatementClosingBrace": ">=1",
"ForInStatementExpressionClosing": 0,
"ForInStatementExpressionOpening": 0,
"ForInStatementOpeningBrace": 0,
"ForStatement": ">=1",
"ForStatementClosingBrace": ">=1",
"ForStatementExpressionClosing": "<2",
"ForStatementExpressionOpening": 0,
"ForStatementOpeningBrace": 0,
"FunctionDeclaration": ">=1",
"FunctionDeclarationClosingBrace": ">=1",
"FunctionDeclarationOpeningBrace": 0,
"FunctionExpression": 0,
"FunctionExpressionClosingBrace": 1,
"FunctionExpressionOpeningBrace":0,
"IIFEClosingParentheses": 0,
"IfStatement": ">=1",
"IfStatementClosingBrace": ">=1",
"IfStatementOpeningBrace": 0,
"LogicalExpression": -1,
"MemberExpressionClosing": 0,
"MemberExpressionOpening": 0,
"MemberExpressionPeriod": -1,
"MethodDefinition": ">=1",
"ObjectExpressionClosingBrace": "<=1",
"ObjectPatternClosingBrace": 0,
"ObjectPatternComma": 0,
"ObjectPatternOpeningBrace": 0,
"ParameterDefault": 0,
"Property": "<=2",
"PropertyValue": 0,
"ReturnStatement": -1,
"SwitchClosingBrace": ">=1",
"SwitchOpeningBrace": 0,
"ThisExpression": -1,
"ThrowStatement": ">=1",
"TryClosingBrace": ">=1",
"TryKeyword": -1,
"TryOpeningBrace": 0,
"VariableDeclaration": ">=1",
"VariableDeclarationSemiColon": 0,
"VariableDeclarationWithoutInit": ">=1",
"VariableName": ">=1",
"VariableValue": 0,
"WhileStatement": ">=1",
"WhileStatementClosingBrace": ">=1",
"WhileStatementOpeningBrace": 0
},
"after": {
"ArrayPatternClosing": 0,
"ArrayPatternComma": 0,
"ArrayPatternOpening": 0,
"ArrowFunctionExpressionArrow": 0,
"ArrowFunctionExpressionClosingBrace": -1,
"ArrowFunctionExpressionOpeningBrace": ">=1",
"AssignmentExpression": ">=1",
"AssignmentOperator": 0,
"BlockStatement": 0,
"BreakKeyword": -1,
"CallExpression": -1,
"CallExpressionClosingParentheses": -1,
"CallExpressionOpeningParentheses": -1,
"CatchClosingBrace": ">=0",
"CatchKeyword": 0,
"CatchOpeningBrace": ">=1",
"ClassDeclaration": ">=1",
"ClassDeclarationClosingBrace": ">=1",
"ClassDeclarationOpeningBrace": ">=1",
"ConditionalExpression": ">=1",
"DeleteOperator": ">=1",
"DoWhileStatement": ">=1",
"DoWhileStatementClosingBrace": 0,
"DoWhileStatementOpeningBrace": ">=1",
"ElseIfStatement": ">=1",
"ElseIfStatementClosingBrace": ">=1",
"ElseIfStatementOpeningBrace": ">=1",
"ElseStatement": ">=1",
"ElseStatementClosingBrace": ">=1",
"ElseStatementOpeningBrace": ">=1",
"EmptyStatement": -1,
"FinallyClosingBrace": ">=1",
"FinallyKeyword": -1,
"FinallyOpeningBrace": ">=1",
"ForInStatement": ">=1",
"ForInStatementClosingBrace": ">=1",
"ForInStatementExpressionClosing": -1,
"ForInStatementExpressionOpening": "<2",
"ForInStatementOpeningBrace": ">=1",
"ForStatement": ">=1",
"ForStatementClosingBrace": ">=1",
"ForStatementExpressionClosing": -1,
"ForStatementExpressionOpening": "<2",
"ForStatementOpeningBrace": ">=1",
"FunctionDeclaration": ">=1",
"FunctionDeclarationClosingBrace": ">=1",
"FunctionDeclarationOpeningBrace": ">=1",
"FunctionExpression": 0,
"FunctionExpressionClosingBrace": -1,
"FunctionExpressionOpeningBrace": 1,
"IIFEOpeningParentheses": 0,
"IfStatement": ">=1",
"IfStatementClosingBrace": ">=1",
"IfStatementOpeningBrace": ">=1",
"LogicalExpression": -1,
"MemberExpressionClosing": 0,
"MemberExpressionOpening": 0,
"MemberExpressionPeriod": 0,
"MethodDefinition": ">=1",
"ObjectExpressionOpeningBrace": "<=1",
"ObjectPatternClosingBrace": 0,
"ObjectPatternComma": 0,
"ObjectPatternOpeningBrace": 0,
"ParameterDefault": 0,
"Property": -1,
"PropertyName": 0,
"ReturnStatement": -1,
"SwitchCaseColon": ">=1",
"SwitchClosingBrace": ">=1",
"SwitchOpeningBrace": ">=1",
"ThisExpression": 0,
"ThrowStatement": ">=1",
"TryClosingBrace": 0,
"TryKeyword": -1,
"TryOpeningBrace": ">=1",
"VariableDeclaration": ">=1",
"VariableDeclarationSemiColon": ">=1",
"VariableValue": -1,
"WhileStatement": ">=1",
"WhileStatementClosingBrace": ">=1",
"WhileStatementOpeningBrace": ">=1"
}
},
"whiteSpace": {
"value": " ",
"removeTrailing": 1,
"before": {
"ArgumentComma": 0,
"ArgumentList": 0,
"ArgumentListArrayExpression": 0,
"ArgumentListFunctionExpression": 1,
"ArgumentListObjectExpression": 0,
"ArrayExpressionClosing": 0,
"ArrayExpressionComma": 0,
"ArrayExpressionOpening": 1,
"AssignmentOperator": 1,
"BinaryExpression": 0,
"BinaryExpressionOperator": 1,
"BlockComment": 1,
"CallExpression": 1,
"CatchClosingBrace": 1,
"CatchKeyword": 1,
"CatchOpeningBrace": 1,
"CatchParameterList": 0,
"CommaOperator": 0,
"ConditionalExpressionAlternate": 1,
"ConditionalExpressionConsequent": 1,
"DoWhileStatementClosingBrace": 1,
"DoWhileStatementConditional": 1,
"DoWhileStatementOpeningBrace": 1,
"ElseIfStatementClosingBrace": 1,
"ElseIfStatementOpeningBrace": 1,
"ElseStatementClosingBrace": 1,
"ElseStatementOpeningBrace": 1,
"EmptyStatement": 0,
"ExpressionClosingParentheses": 0,
"FinallyClosingBrace": 1,
"FinallyKeyword": -1,
"FinallyOpeningBrace": 1,
"ForInStatement": 1,
"ForInStatementClosingBrace": 1,
"ForInStatementExpressionClosing": 0,
"ForInStatementExpressionOpening": 1,
"ForInStatementOpeningBrace": 1,
"ForStatement": 1,
"ForStatementClosingBrace": 1,
"ForStatementExpressionClosing": 0,
"ForStatementExpressionOpening": 1,
"ForStatementOpeningBrace": 1,
"ForStatementSemicolon": 0,
"FunctionDeclarationClosingBrace": 1,
"FunctionDeclarationOpeningBrace": 1,
"FunctionExpressionClosingBrace": 1,
"FunctionExpressionOpeningBrace": 1,
"IfStatementClosingBrace": 1,
"IfStatementConditionalClosing": 0,
"IfStatementConditionalOpening": 1,
"IfStatementOpeningBrace": 1,
"LineComment": 1,
"LogicalExpressionOperator": 1,
"MemberExpressionClosing": 0,
"ObjectExpressionClosingBrace": 1,
"ParameterComma": 0,
"ParameterList": 0,
"Property": 1,
"PropertyName": 1,
"PropertyValue": 1,
"SwitchDiscriminantClosing": 0,
"SwitchDiscriminantOpening": 1,
"ThrowKeyword": 1,
"TryClosingBrace": 1,
"TryKeyword": -1,
"TryOpeningBrace": 1,
"UnaryExpressionOperator": 0,
"VariableName": 1,
"VariableValue": 1,
"WhileStatementClosingBrace": 1,
"WhileStatementConditionalClosing": 0,
"WhileStatementConditionalOpening": 1,
"WhileStatementOpeningBrace": 1
},
"after": {
"ArgumentComma": 1,
"ArgumentList": 0,
"ArgumentListArrayExpression": 1,
"ArgumentListFunctionExpression": 1,
"ArgumentListObjectExpression": 0,
"ArrayExpressionClosing": 0,
"ArrayExpressionComma": 1,
"ArrayExpressionOpening": 0,
"AssignmentOperator": 1,
"BinaryExpression": 0,
"BinaryExpressionOperator": 1,
"BlockComment": 1,
"CallExpression": 0,
"CatchClosingBrace": 1,
"CatchKeyword": 1,
"CatchOpeningBrace": 1,
"CatchParameterList": 0,
"CommaOperator": 1,
"ConditionalExpressionConsequent": 1,
"ConditionalExpressionTest": 1,
"DoWhileStatementBody": 1,
"DoWhileStatementClosingBrace": 1,
"DoWhileStatementOpeningBrace": 1,
"ElseIfStatementClosingBrace": 1,
"ElseIfStatementOpeningBrace": 1,
"ElseStatementClosingBrace": 1,
"ElseStatementOpeningBrace": 1,
"EmptyStatement": 0,
"ExpressionOpeningParentheses": 0,
"FinallyClosingBrace": 1,
"FinallyKeyword": -1,
"FinallyOpeningBrace": 1,
"ForInStatement": 1,
"ForInStatementClosingBrace": 1,
"ForInStatementExpressionClosing": 1,
"ForInStatementExpressionOpening": 0,
"ForInStatementOpeningBrace": 1,
"ForStatement": 1,
"ForStatementClosingBrace": 1,
"ForStatementExpressionClosing": 1,
"ForStatementExpressionOpening": 0,
"ForStatementOpeningBrace": 1,
"ForStatementSemicolon": 1,
"FunctionDeclarationClosingBrace": 0,
"FunctionDeclarationOpeningBrace": 0,
"FunctionExpressionClosingBrace": 0,
"FunctionExpressionOpeningBrace": 0,
"FunctionName": 0,
"FunctionReservedWord": 0,
"IfStatementClosingBrace": 1,
"IfStatementConditionalClosing": 0,
"IfStatementConditionalOpening": 0,
"IfStatementOpeningBrace": 1,
"LogicalExpressionOperator": 1,
"MemberExpressionOpening": 0,
"ObjectExpressionClosingBrace": 0,
"ObjectExpressionOpeningBrace": 1,
"ParameterComma": 1,
"ParameterList": 0,
"PropertyName": 0,
"PropertyValue": 0,
"SwitchDiscriminantClosing": 1,
"SwitchDiscriminantOpening": 0,
"ThrowKeyword": 1,
"TryClosingBrace": 1,
"TryKeyword": -1,
"TryOpeningBrace": 1,
"UnaryExpressionOperator": 0,
"VariableName": 1,
"WhileStatementClosingBrace": 1,
"WhileStatementConditionalClosing": 1,
"WhileStatementConditionalOpening": 0,
"WhileStatementOpeningBrace": 1
}
}
}

View File

@@ -1,17 +1,14 @@
const fs = require('fs');
const path = require('path');
const frontendFolder = __dirname;
const dirs = fs
.readdirSync(path.join(frontendFolder, 'src'), { withFileTypes: true })
.readdirSync('frontend/src', { withFileTypes: true })
.filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name)
.join('|');
module.exports = {
root: true,
const frontendFolder = __dirname;
module.exports = {
parser: '@babel/eslint-parser',
env: {
@@ -31,7 +28,7 @@ module.exports = {
ecmaVersion: 6,
sourceType: 'module',
babelOptions: {
configFile: `${frontendFolder}/babel.config.js`
configFile: `${frontendFolder}/babel.config.js`,
},
ecmaFeatures: {
modules: true,

12
frontend/.jsbeautifyrc Normal file
View File

@@ -0,0 +1,12 @@
{
"js": {
"indent_size": 2,
"indent_char": " ",
"indent_level": 2,
"indent_with_tabs": false,
"preserve_newlines": true,
"brace_style": "collapse",
"max_preserve_newlines": 2,
"jslint_happy": true
}
}

View File

@@ -1,12 +1,12 @@
{
"plugins": [
"stylelint-order"
],
"ignoreFiles": [
"frontend/src/Styles/scaffolding.css",
"**/*.js"
],
"rules": {
"plugins": [
"stylelint-order"
],
"ignoreFiles": [
"frontend/src/Styles/scaffolding.css",
"**/*.js"
],
"rules": {
"at-rule-empty-line-before": [
"always",
{
@@ -15,6 +15,9 @@
]
}
],
"at-rule-name-case": "lower",
"at-rule-name-newline-after": "always-multi-line",
"at-rule-name-space-after": "always",
"at-rule-no-unknown": [
true,
{
@@ -25,36 +28,83 @@
}
],
"at-rule-no-vendor-prefix": true,
"at-rule-semicolon-newline-after": "always",
"at-rule-semicolon-space-before": "never",
"block-closing-brace-empty-line-before": "never",
"block-closing-brace-newline-after": "always",
"block-closing-brace-newline-before": "always",
"block-closing-brace-space-after": "always-single-line",
"block-closing-brace-space-before": "always-single-line",
"block-no-empty": true,
"block-opening-brace-newline-after": "always",
"block-opening-brace-newline-before": "never-single-line",
"block-opening-brace-space-after": "always-single-line",
"block-opening-brace-space-before": "always",
"color-hex-case": "lower",
"color-hex-length": "short",
"color-named": "never",
"color-no-invalid-hex": true,
"comment-whitespace-inside": "always",
"declaration-bang-space-after": "never",
"declaration-bang-space-before": "always",
"declaration-block-no-duplicate-properties": [
true,
{
"ignoreProperties": [
"composes"
"composes"
]
}
],
"declaration-block-no-redundant-longhand-properties": true,
"declaration-block-no-shorthand-property-overrides": true,
"declaration-block-semicolon-newline-after": "always",
"declaration-block-semicolon-newline-before": "never-multi-line",
"declaration-block-semicolon-space-before": "never",
"declaration-block-single-line-max-declarations": 1,
"declaration-block-trailing-semicolon": "always",
"declaration-colon-space-after": "always",
"declaration-colon-space-before": "never",
"font-family-name-quotes": "always-unless-keyword",
"function-calc-no-unspaced-operator": true,
"function-comma-newline-after": "never-multi-line",
"function-comma-newline-before": "never-multi-line",
"function-comma-space-after": "always",
"function-comma-space-before": "never",
"function-linear-gradient-no-nonstandard-direction": true,
"function-name-case": "lower",
"function-parentheses-newline-inside": "never-multi-line",
"function-parentheses-space-inside": "never",
"function-url-quotes": "always",
"function-url-scheme-disallowed-list": [
"data"
],
"function-whitespace-after": "always",
"indentation": 2,
"keyframe-declaration-no-important": true,
"length-zero-no-unit": true,
"max-empty-lines": 1,
"max-line-length": [
100,
{
"ignore": [
"non-comments"
]
}
],
"max-nesting-depth": 2,
"media-feature-colon-space-after": "always",
"media-feature-colon-space-before": "never",
"media-feature-name-case": "lower",
"media-feature-name-no-vendor-prefix": true,
"media-feature-range-operator-space-after": "always",
"media-feature-range-operator-space-before": "always",
"no-empty-source": true,
"no-eol-whitespace": true,
"no-extra-semicolons": true,
"no-invalid-double-slash-comments": true,
"no-missing-end-of-source-newline": true,
"number-leading-zero": "always",
"number-no-trailing-zeros": true,
"order/order": [
"custom-properties",
"dollar-variables",
@@ -82,7 +132,6 @@
"right",
"bottom",
"left",
"inset",
"z-index",
"display",
"visibility",
@@ -294,33 +343,54 @@
]
}
],
"property-case": "lower",
"property-no-vendor-prefix": true,
"rule-empty-line-before": [
"always",
{
"except": [
"first-nested"
"first-nested"
],
"ignore": [
"after-comment"
"after-comment"
]
}
],
"selector-attribute-brackets-space-inside": "never",
"selector-attribute-operator-space-after": "never",
"selector-attribute-operator-space-before": "never",
"selector-attribute-quotes": "never",
"selector-class-pattern": "^[A-Za-z0-9]+$",
"selector-combinator-space-after": "always",
"selector-combinator-space-before": "always",
"selector-descendant-combinator-no-non-space": true,
"selector-list-comma-newline-after": "always",
"selector-list-comma-newline-before": "never-multi-line",
"selector-list-comma-space-before": "never",
"selector-max-attribute": 0,
"selector-max-class": 3,
"selector-max-compound-selectors": 3,
"selector-max-empty-lines": 0,
"selector-max-id": 0,
"selector-max-universal": 0,
"selector-pseudo-class-case": "lower",
"selector-pseudo-class-parentheses-space-inside": "never",
"selector-pseudo-element-case": "lower",
"selector-pseudo-element-colon-notation": "double",
"selector-pseudo-element-no-unknown": true,
"selector-type-case": "lower",
"selector-type-no-unknown": true,
"shorthand-property-no-redundant-values": true,
"string-no-newline": true,
"string-quotes": "single",
"time-min-milliseconds": 100,
"unit-case": "lower",
"unit-no-unknown": true,
"value-list-comma-newline-after": "never-multi-line",
"value-list-comma-newline-before": "never-multi-line",
"value-list-comma-space-after": "always",
"value-list-comma-space-before": "never",
"value-list-max-empty-lines": 0,
"value-no-vendor-prefix": true
}
}
}

View File

@@ -1,7 +0,0 @@
{
"recommendations": [
"stylelint.vscode-stylelint",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}

View File

@@ -44,7 +44,7 @@ module.exports = (env) => {
'node_modules'
],
alias: {
jquery: 'jquery/dist/jquery.min',
jquery: 'jquery/src/jquery',
'react-middle-truncate': 'react-middle-truncate/lib/react-middle-truncate'
},
fallback: {
@@ -253,19 +253,18 @@ module.exports = (env) => {
config.resolve.alias['react-dom$'] = 'react-dom/profiling';
config.resolve.alias['scheduler/tracing'] = 'scheduler/tracing-profiling';
config.optimization = {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
sourceMap: true, // Must be set to true if using source-maps in production
mangle: false,
keep_classnames: true,
keep_fnames: true
}
})
]
};
config.optimization.minimizer = [
new TerserPlugin({
cache: true,
parallel: true,
sourceMap: true, // Must be set to true if using source-maps in production
terserOptions: {
mangle: false,
keep_classnames: true,
keep_fnames: true
}
})
];
}
return config;

View File

@@ -1,4 +1,3 @@
// eslint-disable-next-line filenames/match-exported
const loaderUtils = require('loader-utils');
module.exports = function cssVariablesLoader(source) {

View File

@@ -107,7 +107,7 @@ function HistoryDetails(props) {
{
customFormatScore && customFormatScore !== '0' ?
<DescriptionListItem
title={translate('CustomFormatScore')}
title="Custom Format Score"
data={formatPreferredWordScore(customFormatScore)}
/> :
null
@@ -224,7 +224,7 @@ function HistoryDetails(props) {
{
customFormatScore && customFormatScore !== '0' ?
<DescriptionListItem
title={translate('CustomFormatScore')}
title="Custom Format Score"
data={formatPreferredWordScore(customFormatScore)}
/> :
null
@@ -270,7 +270,7 @@ function HistoryDetails(props) {
{
customFormatScore && customFormatScore !== '0' ?
<DescriptionListItem
title={translate('CustomFormatScore')}
title="Custom Format Score"
data={formatPreferredWordScore(customFormatScore)}
/> :
null

View File

@@ -74,7 +74,7 @@ class AuthorDetailsPageConnector extends Component {
if (isFetching && !isPopulated) {
return (
<PageContent title={translate('Loading')}>
<PageContent title='loading'>
<PageContentBody>
<LoadingIndicator />
</PageContentBody>

View File

@@ -1,3 +1,4 @@
import _ from 'lodash';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
@@ -9,11 +10,15 @@ function createMapStateToProps() {
createAuthorSelector(),
createTagsSelector(),
(author, tagList) => {
const tags = author.tags
.map((tagId) => tagList.find((tag) => tag.id === tagId))
.filter((tag) => !!tag)
.map((tag) => tag.label)
.sort((a, b) => a.localeCompare(b));
const tags = _.reduce(author.tags, (acc, tag) => {
const matchingTag = _.find(tagList, { id: tag });
if (matchingTag) {
acc.push(matchingTag.label);
}
return acc;
}, []);
return {
tags

View File

@@ -87,7 +87,7 @@ class BookDetailsPageConnector extends Component {
if ((isFetching || !this.state.hasMounted) ||
(!isFetching && !isPopulated)) {
return (
<PageContent title={translate('Loading')}>
<PageContent title='loading'>
<PageContentBody>
<LoadingIndicator />
</PageContentBody>

View File

@@ -1,6 +1,6 @@
.dayOfWeek {
flex: 1 0 14.28%;
background-color: var(--calendarBackgroundColor);
background-color: var(--calendarBackgroudColor);
text-align: center;
}

View File

@@ -33,7 +33,7 @@ function getUrls(state) {
icalUrl += `tags=${tags.toString()}&`;
}
icalUrl += `pastDays=${pastDays}&futureDays=${futureDays}&apikey=${encodeURIComponent(window.Readarr.apiKey)}`;
icalUrl += `pastDays=${pastDays}&futureDays=${futureDays}&apikey=${window.Readarr.apiKey}`;
const iCalHttpUrl = `${window.location.protocol}//${icalUrl}`;
const iCalWebCalUrl = `webcal://${icalUrl}`;

View File

@@ -17,7 +17,7 @@ class DescriptionListItem extends Component {
} = this.props;
return (
<div>
<span>
<DescriptionListItemTitle
className={titleClassName}
>
@@ -29,7 +29,7 @@ class DescriptionListItem extends Component {
>
{data}
</DescriptionListItemDescription>
</div>
</span>
);
}
}

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import Alert from 'Components/Alert';
import PathInput from 'Components/Form/PathInput';
import Button from 'Components/Link/Button';
@@ -38,7 +39,7 @@ class FileBrowserModalContent extends Component {
constructor(props, context) {
super(props, context);
this._scrollerRef = React.createRef();
this._scrollerNode = null;
this.state = {
isFileBrowserModalOpen: false,
@@ -56,10 +57,21 @@ class FileBrowserModalContent extends Component {
currentPath !== prevState.currentPath
) {
this.setState({ currentPath });
this._scrollerRef.current.scrollTop = 0;
this._scrollerNode.scrollTop = 0;
}
}
//
// Control
setScrollerRef = (ref) => {
if (ref) {
this._scrollerNode = ReactDOM.findDOMNode(ref);
} else {
this._scrollerNode = null;
}
};
//
// Listeners
@@ -131,7 +143,7 @@ class FileBrowserModalContent extends Component {
/>
<Scroller
ref={this._scrollerRef}
ref={this.setScrollerRef}
className={styles.scroller}
scrollDirection={scrollDirections.BOTH}
>

View File

@@ -1,100 +0,0 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchDownloadClients } from 'Store/Actions/settingsActions';
import sortByName from 'Utilities/Array/sortByName';
import EnhancedSelectInput from './EnhancedSelectInput';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.downloadClients,
(state, { includeAny }) => includeAny,
(state, { protocol }) => protocol,
(downloadClients, includeAny, protocolFilter) => {
const {
isFetching,
isPopulated,
error,
items
} = downloadClients;
const filteredItems = items.filter((item) => item.protocol === protocolFilter);
const values = _.map(filteredItems.sort(sortByName), (downloadClient) => {
return {
key: downloadClient.id,
value: downloadClient.name
};
});
if (includeAny) {
values.unshift({
key: 0,
value: '(Any)'
});
}
return {
isFetching,
isPopulated,
error,
values
};
}
);
}
const mapDispatchToProps = {
dispatchFetchDownloadClients: fetchDownloadClients
};
class DownloadClientSelectInputConnector extends Component {
//
// Lifecycle
componentDidMount() {
if (!this.props.isPopulated) {
this.props.dispatchFetchDownloadClients();
}
}
//
// Listeners
onChange = ({ name, value }) => {
this.props.onChange({ name, value: parseInt(value) });
};
//
// Render
render() {
return (
<EnhancedSelectInput
{...this.props}
onChange={this.onChange}
/>
);
}
}
DownloadClientSelectInputConnector.propTypes = {
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
name: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
includeAny: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired,
dispatchFetchDownloadClients: PropTypes.func.isRequired
};
DownloadClientSelectInputConnector.defaultProps = {
includeAny: false,
protocol: 'torrent'
};
export default connect(createMapStateToProps, mapDispatchToProps)(DownloadClientSelectInputConnector);

View File

@@ -113,12 +113,10 @@ class EnhancedSelectInput extends Component {
this._scheduleUpdate();
}
if (!Array.isArray(this.props.value)) {
if (prevProps.value !== this.props.value || prevProps.values !== this.props.values) {
this.setState({
selectedIndex: getSelectedIndex(this.props)
});
}
if (!Array.isArray(this.props.value) && prevProps.value !== this.props.value) {
this.setState({
selectedIndex: getSelectedIndex(this.props)
});
}
}
@@ -334,11 +332,6 @@ class EnhancedSelectInput extends Component {
const isMultiSelect = Array.isArray(value);
const selectedOption = getSelectedOption(selectedIndex, values);
let selectedValue = value;
if (!values.length) {
selectedValue = isMultiSelect ? [] : '';
}
return (
<div>
@@ -379,17 +372,15 @@ class EnhancedSelectInput extends Component {
onPress={this.onPress}
>
{
isFetching ?
isFetching &&
<LoadingIndicator
className={styles.loading}
size={20}
/> :
null
/>
}
{
isFetching ?
null :
!isFetching &&
<Icon
name={icons.CARET_DOWN}
/>
@@ -409,7 +400,7 @@ class EnhancedSelectInput extends Component {
onPress={this.onPress}
>
<SelectedValueComponent
value={selectedValue}
value={value}
values={values}
{...selectedValueOptions}
{...selectedOption}
@@ -427,17 +418,15 @@ class EnhancedSelectInput extends Component {
>
{
isFetching ?
isFetching &&
<LoadingIndicator
className={styles.loading}
size={20}
/> :
null
/>
}
{
isFetching ?
null :
!isFetching &&
<Icon
name={icons.CARET_DOWN}
/>
@@ -516,7 +505,7 @@ class EnhancedSelectInput extends Component {
</Manager>
{
isMobile ?
isMobile &&
<Modal
className={styles.optionsModal}
size={sizes.EXTRA_SMALL}
@@ -566,8 +555,7 @@ class EnhancedSelectInput extends Component {
}
</Scroller>
</ModalBody>
</Modal> :
null
</Modal>
}
</div>
);

View File

@@ -10,7 +10,6 @@ import BookshelfInputConnector from './BookshelfInputConnector';
import CaptchaInputConnector from './CaptchaInputConnector';
import CheckInput from './CheckInput';
import DeviceInputConnector from './DeviceInputConnector';
import DownloadClientSelectInputConnector from './DownloadClientSelectInputConnector';
import EnhancedSelectInput from './EnhancedSelectInput';
import EnhancedSelectInputConnector from './EnhancedSelectInputConnector';
import FormInputHelpText from './FormInputHelpText';
@@ -82,9 +81,6 @@ function getComponent(type) {
case inputTypes.INDEXER_SELECT:
return IndexerSelectInputConnector;
case inputTypes.DOWNLOAD_CLIENT_SELECT:
return DownloadClientSelectInputConnector;
case inputTypes.ROOT_FOLDER_SELECT:
return RootFolderSelectInputConnector;

View File

@@ -24,7 +24,7 @@ function HintedSelectInputSelectedValue(props) {
>
<div className={styles.valueText}>
{
isMultiSelect ?
isMultiSelect &&
value.map((key, index) => {
const v = valuesMap[key];
return (
@@ -32,28 +32,26 @@ function HintedSelectInputSelectedValue(props) {
{v ? v.value : key}
</Label>
);
}) :
null
})
}
{
isMultiSelect ? null : value
!isMultiSelect && value
}
</div>
{
hint != null && includeHint ?
hint != null && includeHint &&
<div className={styles.hintText}>
{hint}
</div> :
null
</div>
}
</EnhancedSelectInputSelectedValue>
);
}
HintedSelectInputSelectedValue.propTypes = {
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number]))]).isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number]))]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
hint: PropTypes.string,
isMultiSelect: PropTypes.bool.isRequired,

View File

@@ -63,7 +63,6 @@ function ProviderFieldFormGroup(props) {
label,
helpText,
helpLink,
placeholder,
value,
type,
advanced,
@@ -96,7 +95,6 @@ function ProviderFieldFormGroup(props) {
label={label}
helpText={helpText}
helpLink={helpLink}
placeholder={placeholder}
value={value}
values={getSelectValues(selectOptions)}
errors={errors}
@@ -122,7 +120,6 @@ ProviderFieldFormGroup.propTypes = {
label: PropTypes.string.isRequired,
helpText: PropTypes.string,
helpLink: PropTypes.string,
placeholder: PropTypes.string,
value: PropTypes.any,
type: PropTypes.string.isRequired,
advanced: PropTypes.bool.isRequired,

View File

@@ -9,17 +9,14 @@ const ADD_NEW_KEY = 'addNew';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.rootFolders,
(state, { value }) => value,
(state, { includeMissingValue }) => includeMissingValue,
(state, { includeNoChange }) => includeNoChange,
(rootFolders, value, includeMissingValue, includeNoChange) => {
(rootFolders, includeNoChange) => {
const values = rootFolders.items.map((rootFolder) => {
return {
key: rootFolder.path,
value: rootFolder.path,
name: rootFolder.name,
freeSpace: rootFolder.freeSpace,
isMissing: false
freeSpace: rootFolder.freeSpace
};
});
@@ -28,8 +25,7 @@ function createMapStateToProps() {
key: 'noChange',
value: '',
name: 'No Change',
isDisabled: true,
isMissing: false
isDisabled: true
});
}
@@ -43,15 +39,6 @@ function createMapStateToProps() {
});
}
if (includeMissingValue && !values.find((v) => v.key === value)) {
values.push({
key: value,
value,
isMissing: true,
isDisabled: true
});
}
values.push({
key: ADD_NEW_KEY,
value: '',

View File

@@ -18,9 +18,3 @@
color: var(--darkGray);
font-size: $smallFontSize;
}
.isMissing {
margin-left: 15px;
color: var(--dangerColor);
font-size: $smallFontSize;
}

View File

@@ -10,7 +10,6 @@ function RootFolderSelectInputOption(props) {
value,
name,
freeSpace,
isMissing,
isMobile,
...otherProps
} = props;
@@ -30,20 +29,11 @@ function RootFolderSelectInputOption(props) {
<div>{text}</div>
{
freeSpace == null ?
null :
freeSpace != null &&
<div className={styles.freeSpace}>
{formatBytes(freeSpace)} Free
</div>
}
{
isMissing ?
<div className={styles.isMissing}>
Missing
</div> :
null
}
</div>
</EnhancedSelectInputOption>
);
@@ -53,7 +43,6 @@ RootFolderSelectInputOption.propTypes = {
name: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
freeSpace: PropTypes.number,
isMissing: PropTypes.bool,
isMobile: PropTypes.bool.isRequired
};

View File

@@ -1,5 +1,8 @@
.inputContainer {
inset: -1px;
top: -1px;
right: -1px;
bottom: -1px;
left: -1px;
display: flex;
align-items: start;
flex-wrap: wrap;

View File

@@ -36,6 +36,7 @@ class TagInputInput extends Component {
<div
ref={forwardedRef}
className={className}
component="div"
onMouseDown={this.onMouseDown}
>
{

View File

@@ -112,12 +112,6 @@ class TextInput extends Component {
this._isMouseTarget = false;
};
onWheel = () => {
if (this.props.type === 'number') {
this._input.blur();
}
};
//
// Render
@@ -167,7 +161,6 @@ class TextInput extends Component {
onKeyUp={this.onKeyUp}
onMouseDown={this.onMouseDown}
onMouseUp={this.onMouseUp}
onWheel={this.onWheel}
/>
);
}

View File

@@ -23,7 +23,6 @@ function IconButton(props) {
className,
isDisabled && styles.isDisabled
)}
aria-label="Table Options Button"
isDisabled={isDisabled}
{...otherProps}
>

View File

@@ -6,7 +6,6 @@ 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 translate from 'Utilities/String/translate';
import styles from './ModalError.css';
function ModalError(props) {
@@ -26,7 +25,7 @@ function ModalError(props) {
messageClassName={styles.message}
detailsClassName={styles.details}
{...otherProps}
message={translate('ThereWasAnErrorLoadingThisItem')}
message='There was an error loading this item'
/>
</ModalBody>

View File

@@ -61,7 +61,6 @@ class PageHeader extends Component {
<img
className={styles.logo}
src={`${window.Readarr.urlBase}/Content/Images/logo.svg`}
alt="Readarr Logo"
/>
</Link>
</div>
@@ -80,7 +79,6 @@ class PageHeader extends Component {
<IconButton
className={styles.donate}
name={icons.HEART}
aria-label="Donate"
to="https://opencollective.com/readarr"
size={14}
/>

View File

@@ -20,7 +20,7 @@ function PageHeaderActionsMenu(props) {
return (
<div>
<Menu alignMenu={align.RIGHT}>
<MenuButton className={styles.menuButton} aria-label="Menu Button">
<MenuButton className={styles.menuButton}>
<Icon
name={icons.INTERACTIVE}
/>

View File

@@ -1,6 +1,5 @@
import React from 'react';
import ErrorBoundaryError from 'Components/Error/ErrorBoundaryError';
import translate from 'Utilities/String/translate';
import PageContentBody from './PageContentBody';
import styles from './PageContentError.css';
@@ -10,7 +9,7 @@ function PageContentError(props) {
<PageContentBody>
<ErrorBoundaryError
{...props}
message={translate('ThereWasAnErrorLoadingThisPage')}
message='There was an error loading this page'
/>
</PageContentBody>
</div>

View File

@@ -19,7 +19,7 @@
}
}
@media only screen and (max-width: $breakpointExtraLarge) {
@media only screen and (max-width: $breakpointLarge) {
.contentFooter {
flex-wrap: wrap;
}

View File

@@ -56,9 +56,7 @@ function ProgressBar(props) {
styles[kind],
enableColorImpairedMode && 'colorImpaired'
)}
role="meter"
aria-label={`Progress Bar at ${progress.toFixed(0)}%`}
aria-valuenow={progress.toFixed(0)}
aria-valuenow={progress}
aria-valuemin="0"
aria-valuemax="100"
style={{ width: progressPercent }}

View File

@@ -62,7 +62,7 @@ function Logger(minimumLogLevel) {
}
Logger.prototype.cleanse = function(message) {
const apikey = new RegExp(`access_token=${encodeURIComponent(window.Readarr.apiKey)}`, 'g');
const apikey = new RegExp(`access_token=${window.Readarr.apiKey}`, 'g');
return message.replace(apikey, 'access_token=(removed)');
};
@@ -106,7 +106,7 @@ class SignalRConnector extends Component {
this.connection = new signalR.HubConnectionBuilder()
.configureLogging(new Logger(signalR.LogLevel.Information))
.withUrl(`${url}?access_token=${encodeURIComponent(window.Readarr.apiKey)}`)
.withUrl(`${url}?access_token=${window.Readarr.apiKey}`)
.withAutomaticReconnect({
nextRetryDelayInMilliseconds: (retryContext) => {
if (retryContext.elapsedMilliseconds > 180000) {

View File

@@ -1,3 +1,4 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React from 'react';
import { kinds } from 'Helpers/Props';
@@ -5,15 +6,16 @@ import Label from './Label';
import styles from './TagList.css';
function TagList({ tags, tagList }) {
const sortedTags = tags
.map((tagId) => tagList.find((tag) => tag.id === tagId))
.filter((tag) => !!tag)
.sort((a, b) => a.label.localeCompare(b.label));
return (
<div className={styles.tags}>
{
sortedTags.map((tag) => {
tags.map((t) => {
const tag = _.find(tagList, { id: t });
if (!tag) {
return null;
}
return (
<Label
key={tag.id}

View File

@@ -14,7 +14,6 @@ export const QUALITY_PROFILE_SELECT = 'qualityProfileSelect';
export const METADATA_PROFILE_SELECT = 'metadataProfileSelect';
export const BOOK_EDITION_SELECT = 'bookEditionSelect';
export const INDEXER_SELECT = 'indexerSelect';
export const DOWNLOAD_CLIENT_SELECT = 'downloadClientSelect';
export const ROOT_FOLDER_SELECT = 'rootFolderSelect';
export const SELECT = 'select';
export const DYNAMIC_SELECT = 'dynamicSelect';
@@ -41,7 +40,6 @@ export const all = [
METADATA_PROFILE_SELECT,
BOOK_EDITION_SELECT,
INDEXER_SELECT,
DOWNLOAD_CLIENT_SELECT,
ROOT_FOLDER_SELECT,
SELECT,
DYNAMIC_SELECT,

View File

@@ -120,7 +120,7 @@ class InteractiveImportModalContentConnector extends Component {
onImportSelectedPress = (selected, importMode) => {
const files = [];
if (importMode === 'chooseImportMode') {
if (importMode === 'chooseImportMethod') {
this.setState({ interactiveImportErrorMessage: 'An import mode must be selected' });
return;
}

View File

@@ -293,7 +293,7 @@ class InteractiveImportRow extends Component {
anchor={
<Icon name={icons.INTERACTIVE} />
}
title={translate('Formats')}
title="Formats"
body={
<div className={styles.customFormatTooltip}>
<BookFormats formats={customFormats} />

View File

@@ -10,7 +10,6 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, scrollDirections } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './SelectReleaseGroupModalContent.css';
class SelectReleaseGroupModalContent extends Component {
@@ -65,9 +64,7 @@ class SelectReleaseGroupModalContent extends Component {
>
<Form>
<FormGroup>
<FormLabel>
{translate('ReleaseGroup')}
</FormLabel>
<FormLabel>Release Group</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}

View File

@@ -10,14 +10,13 @@ import styles from './AdvancedSettingsButton.css';
function AdvancedSettingsButton(props) {
const {
advancedSettings,
onAdvancedSettingsPress,
showLabel
onAdvancedSettingsPress
} = props;
return (
<Link
className={styles.button}
title={advancedSettings ? translate('ShownClickToHide') : translate('HiddenClickToShow')}
title={advancedSettings ? translate('AdvancedSettingsShownClickToHide') : translate('AdvancedSettingsHiddenClickToShow')}
onPress={onAdvancedSettingsPress}
>
<Icon
@@ -44,27 +43,18 @@ function AdvancedSettingsButton(props) {
/>
</span>
{
showLabel ?
<div className={styles.labelContainer}>
<div className={styles.label}>
{advancedSettings ? translate('HideAdvanced') : translate('ShowAdvanced')}
</div>
</div> :
null
}
<div className={styles.labelContainer}>
<div className={styles.label}>
{advancedSettings ? 'Hide Advanced' : 'Show Advanced'}
</div>
</div>
</Link>
);
}
AdvancedSettingsButton.propTypes = {
advancedSettings: PropTypes.bool.isRequired,
onAdvancedSettingsPress: PropTypes.func.isRequired,
showLabel: PropTypes.bool.isRequired
};
AdvancedSettingsButton.defaultProps = {
showLabel: true
onAdvancedSettingsPress: PropTypes.func.isRequired
};
export default AdvancedSettingsButton;

View File

@@ -4,7 +4,6 @@ import { HTML5Backend } from 'react-dnd-html5-backend';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
import translate from 'Utilities/String/translate';
import CustomFormatsConnector from './CustomFormats/CustomFormatsConnector';
class CustomFormatSettingsConnector extends Component {
@@ -14,7 +13,7 @@ class CustomFormatSettingsConnector extends Component {
render() {
return (
<PageContent title={translate('CustomFormatSettings')}>
<PageContent title="Custom Format Settings">
<SettingsToolbarConnector
showSave={false}
/>

View File

@@ -5,7 +5,6 @@ import Label from 'Components/Label';
import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditCustomFormatModalConnector from './EditCustomFormatModalConnector';
import ExportCustomFormatModal from './ExportCustomFormatModal';
import styles from './CustomFormat.css';
@@ -93,14 +92,14 @@ class CustomFormat extends Component {
<div className={styles.buttons}>
<IconButton
className={styles.cloneButton}
title={translate('CloneCustomFormat')}
title="Clone Custom Format"
name={icons.CLONE}
onPress={this.onCloneCustomFormatPress}
/>
<IconButton
className={styles.cloneButton}
title={translate('ExportCustomFormat')}
title="Export Custom Format"
name={icons.EXPORT}
onPress={this.onExportCustomFormatPress}
/>
@@ -151,9 +150,9 @@ class CustomFormat extends Component {
<ConfirmModal
isOpen={this.state.isDeleteCustomFormatModalOpen}
kind={kinds.DANGER}
title={translate('DeleteCustomFormat')}
message={translate('DeleteCustomFormatMessageText', [name])}
confirmLabel={translate('Delete')}
title="Delete Custom Format"
message={`Are you sure you want to delete the custom format '${name}'?`}
confirmLabel="Delete"
isSpinning={isDeleting}
onConfirm={this.onConfirmDeleteCustomFormat}
onCancel={this.onDeleteCustomFormatModalClose}

View File

@@ -5,7 +5,6 @@ import FieldSet from 'Components/FieldSet';
import Icon from 'Components/Icon';
import PageSectionContent from 'Components/Page/PageSectionContent';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import CustomFormat from './CustomFormat';
import EditCustomFormatModalConnector from './EditCustomFormatModalConnector';
import styles from './CustomFormats.css';
@@ -59,9 +58,9 @@ class CustomFormats extends Component {
} = this.props;
return (
<FieldSet legend={translate('CustomFormats')}>
<FieldSet legend="Custom Formats">
<PageSectionContent
errorMessage={translate('UnableToLoadCustomFormats')}
errorMessage="Unable to load custom formats"
{...otherProps}c={true}
>
<div className={styles.customFormats}>

View File

@@ -15,7 +15,6 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { icons, inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import ImportCustomFormatModal from './ImportCustomFormatModal';
import AddSpecificationModal from './Specifications/AddSpecificationModal';
import EditSpecificationModalConnector from './Specifications/EditSpecificationModalConnector';
@@ -142,14 +141,14 @@ class EditCustomFormatModalContent extends Component {
<FormInputGroup
type={inputTypes.CHECK}
name="includeCustomFormatWhenRenaming"
helpText={translate('IncludeCustomFormatWhenRenamingHelpText')}
helpText={'Include in {Custom Formats} renaming format'}
{...includeCustomFormatWhenRenaming}
onChange={onInputChange}
/>
</FormGroup>
</Form>
<FieldSet legend={translate('Conditions')}>
<FieldSet legend={'Conditions'}>
<div className={styles.customFormats}>
{
specifications.map((tag) => {

View File

@@ -8,7 +8,6 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './ExportCustomFormatModalContent.css';
class ExportCustomFormatModalContent extends Component {
@@ -60,7 +59,7 @@ class ExportCustomFormatModalContent extends Component {
<ClipboardButton
className={styles.button}
value={json}
title={translate('CopyToClipboard')}
title="Copy to clipboard"
kind={kinds.DEFAULT}
/>
<Button

View File

@@ -14,7 +14,6 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './EditSpecificationModalContent.css';
function EditSpecificationModalContent(props) {
@@ -99,7 +98,7 @@ function EditSpecificationModalContent(props) {
type={inputTypes.CHECK}
name="negate"
{...negate}
helpText={translate('NegateHelpText', [implementationName])}
helpText={`If checked, the custom format will not apply if this ${implementationName} condition matches.`}
onChange={onInputChange}
/>
</FormGroup>
@@ -113,7 +112,7 @@ function EditSpecificationModalContent(props) {
type={inputTypes.CHECK}
name="required"
{...required}
helpText={translate('RequiredHelpText', [implementationName, implementationName])}
helpText={`This ${implementationName} condition must match for the custom format to apply. Otherwise a single ${implementationName} match is sufficient.`}
onChange={onInputChange}
/>
</FormGroup>

View File

@@ -5,7 +5,6 @@ import Label from 'Components/Label';
import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditSpecificationModalConnector from './EditSpecificationModal';
import styles from './Specification.css';
@@ -78,7 +77,7 @@ class Specification extends Component {
<IconButton
className={styles.cloneButton}
title={translate('Clone')}
title="Clone"
name={icons.CLONE}
onPress={this.onCloneSpecificationPress}
/>
@@ -114,9 +113,9 @@ class Specification extends Component {
<ConfirmModal
isOpen={this.state.isDeleteSpecificationModalOpen}
kind={kinds.DANGER}
title={translate('DeleteFormat')}
message={translate('DeleteFormatMessageText', [name])}
confirmLabel={translate('Delete')}
title="Delete Format"
message={`Are you sure you want to delete format tag ${name} ?`}
confirmLabel="Delete"
onConfirm={this.onConfirmDeleteSpecification}
onCancel={this.onDeleteSpecificationModalClose}
/>

View File

@@ -13,9 +13,3 @@
.labelIcon {
margin-left: 8px;
}
.message {
composes: alert from '~Components/Alert.css';
margin-bottom: 30px;
}

View File

@@ -20,7 +20,6 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import Popover from 'Components/Tooltip/Popover';
import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props';
import formatShortTimeSpan from 'Utilities/Date/formatShortTimeSpan';
import translate from 'Utilities/String/translate';
import styles from './EditImportListModalContent.css';
@@ -75,7 +74,6 @@ function EditImportListModalContent(props) {
id,
name,
enableAutomaticAdd,
minRefreshInterval,
shouldMonitor,
shouldMonitorExisting,
shouldSearch,
@@ -120,13 +118,6 @@ function EditImportListModalContent(props) {
</Alert>
}
<Alert
kind={kinds.INFO}
className={styles.message}
>
{translate('ListWillRefreshEveryInterp', [formatShortTimeSpan(minRefreshInterval.value)])}
</Alert>
<FieldSet legend={translate('ImportListSettings')} >
<FormGroup>
<FormLabel>
@@ -222,7 +213,6 @@ function EditImportListModalContent(props) {
name="rootFolderPath"
helpText={translate('RootFolderPathHelpText')}
{...rootFolderPath}
includeMissingValue={true}
onChange={onInputChange}
/>
</FormGroup>

View File

@@ -4,7 +4,6 @@ import Card from 'Components/Card';
import Label from 'Components/Label';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import { kinds } from 'Helpers/Props';
import formatShortTimeSpan from 'Utilities/Date/formatShortTimeSpan';
import translate from 'Utilities/String/translate';
import EditImportListModalConnector from './EditImportListModalConnector';
import styles from './ImportList.css';
@@ -57,7 +56,6 @@ class ImportList extends Component {
id,
name,
enableAutomaticAdd,
minRefreshInterval,
shouldSearch
} = this.props;
@@ -87,15 +85,6 @@ class ImportList extends Component {
}
</div>
<div className={styles.enabled}>
<Label
kind={kinds.INFO}
title={translate('ListRefreshInterval')}
>
{`${translate('Refresh')}: ${formatShortTimeSpan(minRefreshInterval)}`}
</Label>
</div>
<EditImportListModalConnector
id={id}
isOpen={this.state.isEditImportListModalOpen}
@@ -121,7 +110,6 @@ ImportList.propTypes = {
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
enableAutomaticAdd: PropTypes.bool.isRequired,
minRefreshInterval: PropTypes.string.isRequired,
shouldSearch: PropTypes.bool.isRequired,
onConfirmDeleteImportList: PropTypes.func.isRequired
};

View File

@@ -13,7 +13,6 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import AdvancedSettingsButton from 'Settings/AdvancedSettingsButton';
import translate from 'Utilities/String/translate';
import styles from './EditIndexerModalContent.css';
@@ -32,7 +31,6 @@ function EditIndexerModalContent(props) {
onSavePress,
onTestPress,
onDeleteIndexerPress,
onAdvancedSettingsPress,
...otherProps
} = props;
@@ -45,11 +43,8 @@ function EditIndexerModalContent(props) {
enableInteractiveSearch,
supportsRss,
supportsSearch,
tags,
fields,
priority,
protocol,
downloadClientId
priority
} = item;
return (
@@ -149,7 +144,6 @@ function EditIndexerModalContent(props) {
);
})
}
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
@@ -168,37 +162,6 @@ function EditIndexerModalContent(props) {
onChange={onInputChange}
/>
</FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>{translate('DownloadClient')}</FormLabel>
<FormInputGroup
type={inputTypes.DOWNLOAD_CLIENT_SELECT}
name="downloadClientId"
helpText={translate('IndexerDownloadClientHelpText')}
{...downloadClientId}
includeAny={true}
protocol={protocol.value}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{translate('Tags')}
</FormLabel>
<FormInputGroup
type={inputTypes.TAG}
name="tags"
helpText={translate('IndexerTagsHelpText')}
{...tags}
onChange={onInputChange}
/>
</FormGroup>
</Form>
}
</ModalBody>
@@ -214,12 +177,6 @@ function EditIndexerModalContent(props) {
</Button>
}
<AdvancedSettingsButton
advancedSettings={advancedSettings}
onAdvancedSettingsPress={onAdvancedSettingsPress}
showLabel={false}
/>
<SpinnerErrorButton
isSpinning={isTesting}
error={saveError}
@@ -259,7 +216,6 @@ EditIndexerModalContent.propTypes = {
onModalClose: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
onTestPress: PropTypes.func.isRequired,
onAdvancedSettingsPress: PropTypes.func.isRequired,
onDeleteIndexerPress: PropTypes.func
};

View File

@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { saveIndexer, setIndexerFieldValue, setIndexerValue, testIndexer, toggleAdvancedSettings } from 'Store/Actions/settingsActions';
import { saveIndexer, setIndexerFieldValue, setIndexerValue, testIndexer } from 'Store/Actions/settingsActions';
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
import EditIndexerModalContent from './EditIndexerModalContent';
@@ -23,8 +23,7 @@ const mapDispatchToProps = {
setIndexerValue,
setIndexerFieldValue,
saveIndexer,
testIndexer,
toggleAdvancedSettings
testIndexer
};
class EditIndexerModalContentConnector extends Component {
@@ -57,10 +56,6 @@ class EditIndexerModalContentConnector extends Component {
this.props.testIndexer({ id: this.props.id });
};
onAdvancedSettingsPress = () => {
this.props.toggleAdvancedSettings();
};
//
// Render
@@ -70,7 +65,6 @@ class EditIndexerModalContentConnector extends Component {
{...this.props}
onSavePress={this.onSavePress}
onTestPress={this.onTestPress}
onAdvancedSettingsPress={this.onAdvancedSettingsPress}
onInputChange={this.onInputChange}
onFieldChange={this.onFieldChange}
/>
@@ -86,7 +80,6 @@ EditIndexerModalContentConnector.propTypes = {
item: PropTypes.object.isRequired,
setIndexerValue: PropTypes.func.isRequired,
setIndexerFieldValue: PropTypes.func.isRequired,
toggleAdvancedSettings: PropTypes.func.isRequired,
saveIndexer: PropTypes.func.isRequired,
testIndexer: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired

View File

@@ -4,7 +4,6 @@ import Card from 'Components/Card';
import Label from 'Components/Label';
import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import TagList from 'Components/TagList';
import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditIndexerModalConnector from './EditIndexerModalConnector';
@@ -69,8 +68,6 @@ class Indexer extends Component {
enableRss,
enableAutomaticSearch,
enableInteractiveSearch,
tags,
tagList,
supportsRss,
supportsSearch,
priority,
@@ -136,11 +133,6 @@ class Indexer extends Component {
}
</div>
<TagList
tags={tags}
tagList={tagList}
/>
<EditIndexerModalConnector
id={id}
isOpen={this.state.isEditIndexerModalOpen}
@@ -169,8 +161,6 @@ Indexer.propTypes = {
enableRss: PropTypes.bool.isRequired,
enableAutomaticSearch: PropTypes.bool.isRequired,
enableInteractiveSearch: PropTypes.bool.isRequired,
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
supportsRss: PropTypes.bool.isRequired,
supportsSearch: PropTypes.bool.isRequired,
showPriority: PropTypes.bool.isRequired,

View File

@@ -54,7 +54,6 @@ class Indexers extends Component {
render() {
const {
items,
tagList,
dispatchCloneIndexer,
onConfirmDeleteIndexer,
...otherProps
@@ -80,7 +79,6 @@ class Indexers extends Component {
<Indexer
key={item.id}
{...item}
tagList={tagList}
showPriority={showPriority}
onCloneIndexerPress={this.onCloneIndexerPress}
onConfirmDeleteIndexer={onConfirmDeleteIndexer}
@@ -121,7 +119,6 @@ Indexers.propTypes = {
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
dispatchCloneIndexer: PropTypes.func.isRequired,
onConfirmDeleteIndexer: PropTypes.func.isRequired
};

View File

@@ -4,20 +4,13 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { cloneIndexer, deleteIndexer, fetchIndexers } from 'Store/Actions/settingsActions';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
import sortByName from 'Utilities/Array/sortByName';
import Indexers from './Indexers';
function createMapStateToProps() {
return createSelector(
createSortedSectionSelector('settings.indexers', sortByName),
createTagsSelector(),
(indexers, tagList) => {
return {
...indexers,
tagList
};
}
(indexers) => indexers
);
}

View File

@@ -3,7 +3,6 @@ import React, { Component } from 'react';
import Card from 'Components/Card';
import Label from 'Components/Label';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import TagList from 'Components/TagList';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import EditNotificationModalConnector from './EditNotificationModalConnector';
@@ -81,9 +80,7 @@ class Notification extends Component {
supportsOnDownloadFailure,
supportsOnImportFailure,
supportsOnBookRetag,
supportsOnApplicationUpdate,
tags,
tagList
supportsOnApplicationUpdate
} = this.props;
return (
@@ -211,11 +208,6 @@ class Notification extends Component {
null
}
<TagList
tags={tags}
tagList={tagList}
/>
<EditNotificationModalConnector
id={id}
isOpen={this.state.isEditNotificationModalOpen}
@@ -266,8 +258,6 @@ Notification.propTypes = {
supportsOnImportFailure: PropTypes.bool.isRequired,
supportsOnBookRetag: PropTypes.bool.isRequired,
supportsOnApplicationUpdate: PropTypes.bool.isRequired,
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
onConfirmDeleteNotification: PropTypes.func.isRequired
};

View File

@@ -49,7 +49,6 @@ class Notifications extends Component {
render() {
const {
items,
tagList,
onConfirmDeleteNotification,
...otherProps
} = this.props;
@@ -72,7 +71,6 @@ class Notifications extends Component {
<Notification
key={item.id}
{...item}
tagList={tagList}
onConfirmDeleteNotification={onConfirmDeleteNotification}
/>
);
@@ -111,7 +109,6 @@ Notifications.propTypes = {
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
onConfirmDeleteNotification: PropTypes.func.isRequired
};

View File

@@ -4,20 +4,13 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { deleteNotification, fetchNotifications } from 'Store/Actions/settingsActions';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
import sortByName from 'Utilities/Array/sortByName';
import Notifications from './Notifications';
function createMapStateToProps() {
return createSelector(
createSortedSectionSelector('settings.notifications', sortByName),
createTagsSelector(),
(notifications, tagList) => {
return {
...notifications,
tagList
};
}
(notifications) => notifications
);
}

View File

@@ -214,7 +214,7 @@ class EditQualityProfileModalContent extends Component {
type={inputTypes.NUMBER}
name="minFormatScore"
{...minFormatScore}
helpText={translate('MinFormatScoreHelpText')}
helpText="Minimum custom format score allowed to download"
onChange={onInputChange}
/>
</FormGroup>
@@ -231,14 +231,14 @@ class EditQualityProfileModalContent extends Component {
type={inputTypes.NUMBER}
name="cutoffFormatScore"
{...cutoffFormatScore}
helpText={translate('CutoffFormatScoreHelpText')}
helpText="Once this custom format score is reached Readarr will no longer grab book releases"
onChange={onInputChange}
/>
</FormGroup>
}
<div className={styles.formatItemLarge}>
{getCustomFormatRender(formatItems, otherProps)}
{getCustomFormatRender(formatItems, ...otherProps)}
</div>
</div>

View File

@@ -72,7 +72,7 @@ class Quality extends Component {
<PageToolbarSeparator />
<PageToolbarButton
label={translate('ResetDefinitions')}
label="Reset Definitions"
iconName={icons.REFRESH}
isSpinning={isResettingQualityDefinitions}
onPress={this.onResetQualityDefinitionsPress}

View File

@@ -9,7 +9,6 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './ResetQualityDefinitionsModalContent.css';
class ResetQualityDefinitionsModalContent extends Component {
@@ -64,15 +63,13 @@ class ResetQualityDefinitionsModalContent extends Component {
</div>
<FormGroup>
<FormLabel>
{translate('ResetTitles')}
</FormLabel>
<FormLabel>Reset Titles</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="resetDefinitionTitles"
value={resetDefinitionTitles}
helpText={translate('ResetDefinitionTitlesHelpText')}
helpText="Reset definition titles as well as values"
onChange={this.onResetDefinitionTitlesChange}
/>
</FormGroup>

View File

@@ -21,7 +21,6 @@ function TagDetailsModalContent(props) {
importLists,
notifications,
releaseProfiles,
indexers,
onModalClose,
onDeleteTagPress
} = props;
@@ -41,7 +40,7 @@ function TagDetailsModalContent(props) {
}
{
author.length ?
!!author.length &&
<FieldSet legend={translate('Authors')}>
{
author.map((item) => {
@@ -52,12 +51,11 @@ function TagDetailsModalContent(props) {
);
})
}
</FieldSet> :
null
</FieldSet>
}
{
delayProfiles.length ?
!!delayProfiles.length &&
<FieldSet legend={translate('DelayProfile')}>
{
delayProfiles.map((item) => {
@@ -82,12 +80,11 @@ function TagDetailsModalContent(props) {
);
})
}
</FieldSet> :
null
</FieldSet>
}
{
notifications.length ?
!!notifications.length &&
<FieldSet legend={translate('Connections')}>
{
notifications.map((item) => {
@@ -98,12 +95,11 @@ function TagDetailsModalContent(props) {
);
})
}
</FieldSet> :
null
</FieldSet>
}
{
importLists.length ?
!!importLists.length &&
<FieldSet legend={translate('ImportLists')}>
{
importLists.map((item) => {
@@ -114,12 +110,11 @@ function TagDetailsModalContent(props) {
);
})
}
</FieldSet> :
null
</FieldSet>
}
{
releaseProfiles.length ?
!!releaseProfiles.length &&
<FieldSet legend={translate('ReleaseProfiles')}>
{
releaseProfiles.map((item) => {
@@ -155,30 +150,13 @@ function TagDetailsModalContent(props) {
</Label>
);
})
}s
}
</div>
</div>
);
})
}
</FieldSet> :
null
}
{
indexers.length ?
<FieldSet legend={translate('Indexers')}>
{
indexers.map((item) => {
return (
<div key={item.id}>
{item.name}
</div>
);
})
}
</FieldSet> :
null
</FieldSet>
}
</ModalBody>
@@ -213,7 +191,6 @@ TagDetailsModalContent.propTypes = {
importLists: PropTypes.arrayOf(PropTypes.object).isRequired,
notifications: PropTypes.arrayOf(PropTypes.object).isRequired,
releaseProfiles: PropTypes.arrayOf(PropTypes.object).isRequired,
indexers: PropTypes.arrayOf(PropTypes.object).isRequired,
onModalClose: PropTypes.func.isRequired,
onDeleteTagPress: PropTypes.func.isRequired
};

View File

@@ -69,14 +69,6 @@ function createMatchingReleaseProfilesSelector() {
);
}
function createMatchingIndexersSelector() {
return createSelector(
(state, { indexerIds }) => indexerIds,
(state) => state.settings.indexers.items,
findMatchingItems
);
}
function createMapStateToProps() {
return createSelector(
createMatchingAuthorSelector(),
@@ -84,15 +76,13 @@ function createMapStateToProps() {
createMatchingImportListsSelector(),
createMatchingNotificationsSelector(),
createMatchingReleaseProfilesSelector(),
createMatchingIndexersSelector(),
(author, delayProfiles, importLists, notifications, releaseProfiles, indexers) => {
(author, delayProfiles, importLists, notifications, releaseProfiles) => {
return {
author,
delayProfiles,
importLists,
notifications,
releaseProfiles,
indexers
releaseProfiles
};
}
);

View File

@@ -57,7 +57,6 @@ class Tag extends Component {
importListIds,
notificationIds,
restrictionIds,
indexerIds,
authorIds
} = this.props;
@@ -71,7 +70,6 @@ class Tag extends Component {
importListIds.length ||
notificationIds.length ||
restrictionIds.length ||
indexerIds.length ||
authorIds.length
);
@@ -122,14 +120,6 @@ class Tag extends Component {
{restrictionIds.length} restriction{restrictionIds.length > 1 && 's'}
</div>
}
{
indexerIds.length ?
<div>
{indexerIds.length} indexer{indexerIds.length > 1 && 's'}
</div> :
null
}
</div>
}
@@ -148,7 +138,6 @@ class Tag extends Component {
importListIds={importListIds}
notificationIds={notificationIds}
restrictionIds={restrictionIds}
indexerIds={indexerIds}
isOpen={isDetailsModalOpen}
onModalClose={this.onDetailsModalClose}
onDeleteTagPress={this.onDeleteTagPress}
@@ -175,7 +164,6 @@ Tag.propTypes = {
importListIds: PropTypes.arrayOf(PropTypes.number).isRequired,
notificationIds: PropTypes.arrayOf(PropTypes.number).isRequired,
restrictionIds: PropTypes.arrayOf(PropTypes.number).isRequired,
indexerIds: PropTypes.arrayOf(PropTypes.number).isRequired,
authorIds: PropTypes.arrayOf(PropTypes.number).isRequired,
onConfirmDeleteTag: PropTypes.func.isRequired
};
@@ -185,7 +173,6 @@ Tag.defaultProps = {
importListIds: [],
notificationIds: [],
restrictionIds: [],
indexerIds: [],
authorIds: []
};

View File

@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchDelayProfiles, fetchImportLists, fetchIndexers, fetchNotifications, fetchReleaseProfiles } from 'Store/Actions/settingsActions';
import { fetchDelayProfiles, fetchImportLists, fetchNotifications, fetchReleaseProfiles } from 'Store/Actions/settingsActions';
import { fetchTagDetails } from 'Store/Actions/tagActions';
import Tags from './Tags';
@@ -29,8 +29,7 @@ const mapDispatchToProps = {
dispatchFetchDelayProfiles: fetchDelayProfiles,
dispatchFetchImportLists: fetchImportLists,
dispatchFetchNotifications: fetchNotifications,
dispatchFetchReleaseProfiles: fetchReleaseProfiles,
dispatchFetchIndexers: fetchIndexers
dispatchFetchReleaseProfiles: fetchReleaseProfiles
};
class MetadatasConnector extends Component {
@@ -44,8 +43,7 @@ class MetadatasConnector extends Component {
dispatchFetchDelayProfiles,
dispatchFetchImportLists,
dispatchFetchNotifications,
dispatchFetchReleaseProfiles,
dispatchFetchIndexers
dispatchFetchReleaseProfiles
} = this.props;
dispatchFetchTagDetails();
@@ -53,7 +51,6 @@ class MetadatasConnector extends Component {
dispatchFetchImportLists();
dispatchFetchNotifications();
dispatchFetchReleaseProfiles();
dispatchFetchIndexers();
}
//
@@ -73,8 +70,7 @@ MetadatasConnector.propTypes = {
dispatchFetchDelayProfiles: PropTypes.func.isRequired,
dispatchFetchImportLists: PropTypes.func.isRequired,
dispatchFetchNotifications: PropTypes.func.isRequired,
dispatchFetchReleaseProfiles: PropTypes.func.isRequired,
dispatchFetchIndexers: PropTypes.func.isRequired
dispatchFetchReleaseProfiles: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(MetadatasConnector);

View File

@@ -198,9 +198,7 @@ class UISettings extends Component {
</FormGroup>
<FormGroup>
<FormLabel>
{translate('EnableColorImpairedMode')}
</FormLabel>
<FormLabel>Enable Color-Impaired Mode</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="enableColorImpairedMode"

View File

@@ -93,29 +93,29 @@ module.exports = {
defaultButtonTextColor: '#eee',
defaultBackgroundColor: '#333',
defaultBorderColor: '#393f45',
defaultBorderColor: '#eaeaea',
defaultHoverBackgroundColor: '#444',
defaultHoverBorderColor: '#5a6265',
defaultHoverBorderColor: '#d6d6d6;',
primaryBackgroundColor: '#0b8750',
primaryBorderColor: '#1d563d',
primaryHoverBackgroundColor: '#097948',
primaryHoverBorderColor: '#1D563D',
primaryHoverBorderColor: '#1D563D;',
successBackgroundColor: '#27c24c',
successBorderColor: '#26be4a',
successHoverBackgroundColor: '#24b145',
successHoverBorderColor: '#1f9c3d',
successHoverBorderColor: '#1f9c3d;',
warningBackgroundColor: '#ff902b',
warningBorderColor: '#ff8d26',
warningHoverBackgroundColor: '#ff8517',
warningHoverBorderColor: '#fc7800',
warningHoverBorderColor: '#fc7800;',
dangerBackgroundColor: '#f05050',
dangerBorderColor: '#f04b4b',
dangerHoverBackgroundColor: '#ee3d3d',
dangerHoverBorderColor: '#ec2626',
dangerHoverBorderColor: '#ec2626;',
iconButtonDisabledColor: '#7a7a7a',
iconButtonHoverColor: '#666',
@@ -207,8 +207,8 @@ module.exports = {
// Calendar
calendarTodayBackgroundColor: '#3e3e3e',
calendarBackgroundColor: '#2a2a2a',
calendarBorderColor: '#393f45',
calendarBackgroudColor: '#2a2a2a',
calendarBorderColor: '#cecece',
calendarTextDim: '#eee',
calendarTextDimAlternate: '#fff',

View File

@@ -91,27 +91,27 @@ module.exports = {
defaultBackgroundColor: '#fff',
defaultBorderColor: '#eaeaea',
defaultHoverBackgroundColor: '#f5f5f5',
defaultHoverBorderColor: '#d6d6d6',
defaultHoverBorderColor: '#d6d6d6;',
primaryBackgroundColor: '#5d9cec',
primaryBorderColor: '#5899eb',
primaryHoverBackgroundColor: '#4b91ea',
primaryHoverBorderColor: '#3483e7',
primaryHoverBorderColor: '#3483e7;',
successBackgroundColor: '#27c24c',
successBorderColor: '#26be4a',
successHoverBackgroundColor: '#24b145',
successHoverBorderColor: '#1f9c3d',
successHoverBorderColor: '#1f9c3d;',
warningBackgroundColor: '#ff902b',
warningBorderColor: '#ff8d26',
warningHoverBackgroundColor: '#ff8517',
warningHoverBorderColor: '#fc7800',
warningHoverBorderColor: '#fc7800;',
dangerBackgroundColor: '#f05050',
dangerBorderColor: '#f04b4b',
dangerHoverBackgroundColor: '#ee3d3d',
dangerHoverBorderColor: '#ec2626',
dangerHoverBorderColor: '#ec2626;',
iconButtonDisabledColor: '#7a7a7a',
iconButtonHoverColor: '#666',
@@ -203,7 +203,7 @@ module.exports = {
// Calendar
calendarTodayBackgroundColor: '#c5c5c5',
calendarBackgroundColor: '#e4eaec',
calendarBackgroudColor: '#e4eaec',
calendarBorderColor: '#cecece',
//

View File

@@ -1,25 +0,0 @@
import moment from 'moment';
function formatShortTimeSpan(timeSpan) {
if (!timeSpan) {
return '';
}
const duration = moment.duration(timeSpan);
const hours = Math.floor(duration.asHours());
const minutes = Math.floor(duration.asMinutes());
const seconds = Math.floor(duration.asSeconds());
if (hours > 0) {
return `${hours} hour(s)`;
}
if (minutes > 0) {
return `${minutes} minute(s)`;
}
return `${seconds} second(s)`;
}
export default formatShortTimeSpan;

View File

@@ -1,4 +1,4 @@
import { filesize } from 'filesize';
import filesize from 'filesize';
function formatBytes(input, showBits = false) {
const size = Number(input);
@@ -7,11 +7,11 @@ function formatBytes(input, showBits = false) {
return '';
}
return `${filesize(size, {
return filesize(size, {
base: 2,
round: 1,
bits: showBits
})}`;
});
}
export default formatBytes;

1
gulpFile.js Normal file
View File

@@ -0,0 +1 @@
require('./frontend/gulp/gulpFile.js');

View File

@@ -5,11 +5,11 @@
"scripts": {
"build": "webpack --config ./frontend/build/webpack.config.js",
"prebuild": "yarn clean",
"clean": "rimraf ./_output/UI && rimraf --glob \"**/*.js.map\"",
"clean": "rimraf ./_output/UI && rimraf \"**/*.js.map\"",
"start": "webpack --watch --config ./frontend/build/webpack.config.js",
"watch": "webpack --watch --config ./frontend/build/webpack.config.js",
"lint": "eslint --config frontend/.eslintrc.js --ignore-path frontend/.eslintignore frontend/",
"lint-fix": "yarn lint --fix",
"lint": "esprint check",
"lint-fix": "esprint check --fix",
"stylelint-linux": "stylelint $(find frontend -name '*.css') --config frontend/.stylelintrc",
"stylelint-windows": "stylelint frontend/**/*.css --config frontend/.stylelintrc"
},
@@ -22,34 +22,37 @@
"readmeFilename": "readme.md",
"main": "index.js",
"browserslist": [
"defaults"
">0.25%",
"not ie 11",
"not op_mini all",
"not chrome < 60"
],
"dependencies": {
"@fortawesome/fontawesome-free": "6.4.0",
"@fortawesome/fontawesome-svg-core": "6.4.0",
"@fortawesome/free-regular-svg-icons": "6.4.0",
"@fortawesome/free-solid-svg-icons": "6.4.0",
"@fortawesome/react-fontawesome": "0.2.0",
"@microsoft/signalr": "6.0.16",
"@sentry/browser": "7.51.2",
"@sentry/integrations": "7.51.2",
"ansi-colors": "4.1.3",
"classnames": "2.3.2",
"clipboard": "2.0.11",
"connected-react-router": "6.9.3",
"@fortawesome/fontawesome-free": "6.1.0",
"@fortawesome/fontawesome-svg-core": "6.1.0",
"@fortawesome/free-regular-svg-icons": "6.1.0",
"@fortawesome/free-solid-svg-icons": "6.1.0",
"@fortawesome/react-fontawesome": "0.1.18",
"@microsoft/signalr": "6.0.7",
"@sentry/browser": "6.18.2",
"@sentry/integrations": "6.18.2",
"ansi-colors": "4.1.1",
"classnames": "2.3.1",
"clipboard": "2.0.10",
"connected-react-router": "6.9.1",
"element-class": "0.2.2",
"filesize": "10.0.7",
"fuse.js": "6.6.2",
"filesize": "7.0.0",
"fuse.js": "6.4.6",
"history": "4.10.1",
"jdu": "1.0.0",
"jquery": "3.7.0",
"jquery": "3.6.0",
"lodash": "4.17.21",
"mobile-detect": "1.4.5",
"moment": "2.29.4",
"mousetrap": "1.6.5",
"normalize.css": "8.0.1",
"prop-types": "15.8.1",
"qs": "6.11.1",
"prop-types": "15.7.2",
"qs": "6.10.3",
"react": "17.0.2",
"react-addons-shallow-compare": "15.6.3",
"react-async-script": "1.2.0",
@@ -79,55 +82,59 @@
"redux-batched-actions": "0.5.0",
"redux-localstorage": "0.4.1",
"redux-thunk": "2.3.0",
"reselect": "4.1.8"
"reselect": "4.0.0"
},
"devDependencies": {
"@babel/core": "7.21.8",
"@babel/eslint-parser": "7.21.8",
"@babel/plugin-proposal-class-properties": "7.18.6",
"@babel/plugin-proposal-export-default-from": "7.18.10",
"@babel/plugin-proposal-export-namespace-from": "7.18.9",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6",
"@babel/plugin-proposal-optional-chaining": "7.21.0",
"@babel/core": "7.17.8",
"@babel/eslint-parser": "7.17.0",
"@babel/plugin-proposal-class-properties": "7.16.7",
"@babel/plugin-proposal-decorators": "7.17.8",
"@babel/plugin-proposal-export-default-from": "7.16.7",
"@babel/plugin-proposal-export-namespace-from": "7.16.7",
"@babel/plugin-proposal-function-sent": "7.16.7",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.16.7",
"@babel/plugin-proposal-numeric-separator": "7.16.7",
"@babel/plugin-proposal-optional-chaining": "7.16.7",
"@babel/plugin-proposal-throw-expressions": "7.16.7",
"@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/preset-env": "7.21.5",
"@babel/preset-react": "7.18.6",
"autoprefixer": "10.4.14",
"babel-loader": "9.1.2",
"@babel/preset-env": "7.16.11",
"@babel/preset-react": "7.16.7",
"autoprefixer": "10.3.1",
"babel-loader": "8.2.3",
"babel-plugin-inline-classnames": "2.0.1",
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
"core-js": "3.30.2",
"css-loader": "6.7.3",
"eslint": "8.40.0",
"core-js": "3.15.2",
"css-loader": "6.5.1",
"eslint": "8.11.0",
"eslint-plugin-filenames": "1.3.2",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-import": "2.25.4",
"eslint-plugin-json": "3.1.0",
"eslint-plugin-react": "7.32.2",
"eslint-plugin-react": "7.29.4",
"eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-simple-import-sort": "10.0.0",
"eslint-plugin-simple-import-sort": "7.0.0",
"esprint": "3.3.0",
"file-loader": "6.2.0",
"filemanager-webpack-plugin": "8.0.0",
"html-webpack-plugin": "5.5.1",
"filemanager-webpack-plugin": "6.1.4",
"html-webpack-plugin": "5.3.2",
"loader-utils": "^3.2.1",
"mini-css-extract-plugin": "2.7.5",
"postcss": "8.4.23",
"mini-css-extract-plugin": "2.1.0",
"postcss": "8.3.6",
"postcss-color-function": "4.1.0",
"postcss-loader": "7.3.0",
"postcss-mixins": "9.0.4",
"postcss-nested": "6.0.1",
"postcss-simple-vars": "7.0.1",
"postcss-loader": "6.2.0",
"postcss-mixins": "8.1.0",
"postcss-nested": "5.0.6",
"postcss-simple-vars": "6.0.3",
"postcss-url": "10.1.3",
"require-nocache": "1.0.0",
"rimraf": "4.4.1",
"rimraf": "3.0.2",
"run-sequence": "2.2.1",
"streamqueue": "1.1.2",
"style-loader": "3.3.2",
"stylelint": "15.6.1",
"stylelint-order": "6.0.3",
"terser-webpack-plugin": "5.3.8",
"style-loader": "3.3.1",
"stylelint": "14.6.0",
"stylelint-order": "5.0.0",
"url-loader": "4.1.1",
"webpack": "5.82.1",
"webpack-cli": "5.1.1",
"webpack": "5.64.2",
"webpack-cli": "4.9.1",
"webpack-livereload-plugin": "3.0.2",
"worker-loader": "3.0.8"
},

View File

@@ -2,7 +2,6 @@
<!-- Common to all Readarr Projects -->
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
@@ -52,7 +51,7 @@
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<!-- Test projects need bindingRedirects -->
<PropertyGroup Condition="'$(ReadarrOutputType)'=='Test'">
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
@@ -65,7 +64,7 @@
<Product>Readarr</Product>
<Company>readarr.com</Company>
<Copyright>Copyright 2017-$([System.DateTime]::Now.ToString('yyyy')) readarr.com (GNU General Public v3)</Copyright>
<!-- Should be replaced by CI -->
<AssemblyVersion>10.0.0.*</AssemblyVersion>
<AssemblyConfiguration>$(Configuration)-dev</AssemblyConfiguration>
@@ -74,7 +73,7 @@
<GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
<GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<Deterministic Condition="$(AssemblyVersion.EndsWith('*'))">False</Deterministic>
</PropertyGroup>

View File

@@ -4,35 +4,35 @@
<PackageVersion Include="AutoFixture" Version="4.17.0" />
<PackageVersion Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" PrivateAssets="all" />
<PackageVersion Include="Dapper" Version="2.0.123" />
<PackageVersion Include="DryIoc.dll" Version="5.4.0" />
<PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" />
<PackageVersion Include="DryIoc.dll" Version="5.2.0" />
<PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="6.0.2" />
<PackageVersion Include="Equ" Version="2.3.0" />
<PackageVersion Include="FluentAssertions" Version="5.10.3" />
<PackageVersion Include="Servarr.FluentMigrator.Runner" Version="3.3.2.9" />
<PackageVersion Include="Servarr.FluentMigrator.Runner.SQLite" Version="3.3.2.9" />
<PackageVersion Include="Servarr.FluentMigrator.Runner.Postgres" Version="3.3.2.9" />
<PackageVersion Include="FluentValidation" Version="9.5.4" />
<PackageVersion Include="FluentValidation" Version="8.6.2" />
<PackageVersion Include="Ical.Net" Version="4.2.0" />
<PackageVersion Include="ImpromptuInterface" Version="7.0.1" />
<PackageVersion Include="LazyCache" Version="2.4.0" />
<PackageVersion Include="Mailkit" Version="3.6.0" />
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.16" />
<PackageVersion Include="Mailkit" Version="3.3.0" />
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.7" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageVersion Include="Mono.Posix.NETStandard" Version="5.20.1.34-servarr22" />
<PackageVersion Include="Moq" Version="4.17.2" />
<PackageVersion Include="MonoTorrent" Version="2.0.7" />
<PackageVersion Include="NBuilder" Version="6.1.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="NLog.Extensions.Logging" Version="5.2.3" />
<PackageVersion Include="NLog" Version="5.1.4" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.2" />
<PackageVersion Include="NLog.Extensions.Logging" Version="5.1.0" />
<PackageVersion Include="NLog" Version="5.0.5" />
<PackageVersion Include="NLog.Targets.Syslog" Version="7.0.0" />
<PackageVersion Include="Npgsql" Version="6.0.9" />
<PackageVersion Include="Npgsql" Version="6.0.8" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageVersion Include="NUnit" Version="3.13.3" />
<PackageVersion Include="NunitXml.TestLogger" Version="3.0.117" />
@@ -41,13 +41,12 @@
<PackageVersion Include="RestSharp" Version="106.15.0" />
<PackageVersion Include="Selenium.Support" Version="3.141.0" />
<PackageVersion Include="Selenium.WebDriver.ChromeDriver" Version="91.0.4472.10100" />
<PackageVersion Include="Sentry" Version="3.31.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.0.1" />
<PackageVersion Include="Sentry" Version="3.25.0" />
<PackageVersion Include="SharpZipLib" Version="1.3.3" />
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.3" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.1.118" />
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.5.0" />
<PackageVersion Include="System.Buffers" Version="4.5.1" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="6.0.1" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
<PackageVersion Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="17.0.24" />
<PackageVersion Include="System.IO.Abstractions" Version="17.0.24" />
@@ -59,8 +58,8 @@
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" />
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="6.0.0" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="6.0.0" />
<PackageVersion Include="System.Text.Json" Version="6.0.7" />
<PackageVersion Include="System.Text.Json" Version="6.0.5" />
<PackageVersion Include="System.ValueTuple" Version="4.5.0" />
<PackageVersion Include="TagLibSharp-Lidarr" Version="2.2.0.19" />
</ItemGroup>
</Project>
</Project>

View File

@@ -103,7 +103,7 @@ namespace NzbDrone.Common.Test.CacheTests
}
[Test]
[Retry(10)]
[Retry(3)]
[Platform(Exclude = "MacOsX")]
public void should_clear_expired_when_they_expire()
{

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;

View File

@@ -1,5 +1,4 @@
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NUnit.Framework;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Test.Common;
@@ -13,14 +12,14 @@ namespace NzbDrone.Common.Test.EnsureTest
public void EnsureWindowsPath(string path)
{
WindowsOnly();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(path, () => path).IsValidPath();
}
[TestCase(@"/var/user/file with, comma.mp3")]
public void EnsureLinuxPath(string path)
{
PosixOnly();
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(path, () => path).IsValidPath();
}
}
}

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Extensions;

View File

@@ -1,4 +1,5 @@
using FluentAssertions;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using NUnit.Framework;
using NzbDrone.Common.Extensions;

View File

@@ -141,7 +141,7 @@ namespace NzbDrone.Common.Test.Http
var request = new HttpRequest($"https://expired.badssl.com");
Assert.Throws<HttpRequestException>(() => Subject.Execute(request));
ExceptionVerification.ExpectedErrors(1);
ExceptionVerification.ExpectedErrors(2);
}
[Test]

View File

@@ -35,7 +35,6 @@ namespace NzbDrone.Common.Test
[TestCase(@"\\Testserver\Test\file.ext", @"\\Testserver\Test\file.ext")]
[TestCase(@"\\Testserver\Test\file.ext\\", @"\\Testserver\Test\file.ext")]
[TestCase(@"\\Testserver\Test\file.ext \\", @"\\Testserver\Test\file.ext")]
[TestCase(@"//CAPITAL//lower// ", @"\\CAPITAL\lower")]
public void Clean_Path_Windows(string dirty, string clean)
{
WindowsOnly();

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Cache
{

View File

@@ -5,6 +5,8 @@ using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Text;
using System.Threading.Tasks;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Common.Composition

View File

@@ -72,7 +72,7 @@ namespace NzbDrone.Common.Disk
private void CheckFolderExists(string path)
{
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(path, () => path).IsValidPath();
if (!FolderExists(path))
{
@@ -82,7 +82,7 @@ namespace NzbDrone.Common.Disk
private void CheckFileExists(string path)
{
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(path, () => path).IsValidPath();
if (!FileExists(path))
{
@@ -100,19 +100,19 @@ namespace NzbDrone.Common.Disk
public bool FolderExists(string path)
{
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(path, () => path).IsValidPath();
return _fileSystem.Directory.Exists(path);
}
public bool FileExists(string path)
{
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(path, () => path).IsValidPath();
return FileExists(path, PathStringComparison);
}
public bool FileExists(string path, StringComparison stringComparison)
{
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(path, () => path).IsValidPath();
switch (stringComparison)
{
@@ -132,7 +132,7 @@ namespace NzbDrone.Common.Disk
public bool FolderWritable(string path)
{
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(path, () => path).IsValidPath();
try
{
@@ -151,42 +151,42 @@ namespace NzbDrone.Common.Disk
public bool FolderEmpty(string path)
{
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(path, () => path).IsValidPath();
return _fileSystem.Directory.EnumerateFileSystemEntries(path).Empty();
}
public string[] GetDirectories(string path)
{
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(path, () => path).IsValidPath();
return _fileSystem.Directory.GetDirectories(path);
}
public string[] GetDirectories(string path, SearchOption searchOption)
{
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(path, () => path).IsValidPath();
return _fileSystem.Directory.GetDirectories(path, "*", searchOption);
}
public string[] GetFiles(string path, SearchOption searchOption)
{
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(path, () => path).IsValidPath();
return _fileSystem.Directory.GetFiles(path, "*.*", searchOption);
}
public long GetFolderSize(string path)
{
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(path, () => path).IsValidPath();
return GetFiles(path, SearchOption.AllDirectories).Sum(e => _fileSystem.FileInfo.FromFileName(e).Length);
}
public long GetFileSize(string path)
{
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(path, () => path).IsValidPath();
if (!FileExists(path))
{
@@ -199,13 +199,13 @@ namespace NzbDrone.Common.Disk
public void CreateFolder(string path)
{
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(path, () => path).IsValidPath();
_fileSystem.Directory.CreateDirectory(path);
}
public void DeleteFile(string path)
{
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(path, () => path).IsValidPath();
Logger.Trace("Deleting file: {0}", path);
RemoveReadOnly(path);
@@ -215,8 +215,8 @@ namespace NzbDrone.Common.Disk
public void CloneFile(string source, string destination, bool overwrite = false)
{
Ensure.That(source, () => source).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(destination, () => destination).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(source, () => source).IsValidPath();
Ensure.That(destination, () => destination).IsValidPath();
if (source.PathEquals(destination))
{
@@ -233,8 +233,8 @@ namespace NzbDrone.Common.Disk
public void CopyFile(string source, string destination, bool overwrite = false)
{
Ensure.That(source, () => source).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(destination, () => destination).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(source, () => source).IsValidPath();
Ensure.That(destination, () => destination).IsValidPath();
if (source.PathEquals(destination))
{
@@ -251,8 +251,8 @@ namespace NzbDrone.Common.Disk
public void MoveFile(string source, string destination, bool overwrite = false)
{
Ensure.That(source, () => source).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(destination, () => destination).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(source, () => source).IsValidPath();
Ensure.That(destination, () => destination).IsValidPath();
if (source.PathEquals(destination))
{
@@ -270,8 +270,8 @@ namespace NzbDrone.Common.Disk
public void MoveFolder(string source, string destination)
{
Ensure.That(source, () => source).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(destination, () => destination).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(source, () => source).IsValidPath();
Ensure.That(destination, () => destination).IsValidPath();
Directory.Move(source, destination);
}
@@ -300,7 +300,7 @@ namespace NzbDrone.Common.Disk
public void DeleteFolder(string path, bool recursive)
{
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(path, () => path).IsValidPath();
var files = _fileSystem.Directory.GetFiles(path, "*.*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
Array.ForEach(files, RemoveReadOnly);
@@ -310,14 +310,14 @@ namespace NzbDrone.Common.Disk
public string ReadAllText(string filePath)
{
Ensure.That(filePath, () => filePath).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(filePath, () => filePath).IsValidPath();
return _fileSystem.File.ReadAllText(filePath);
}
public void WriteAllText(string filename, string contents)
{
Ensure.That(filename, () => filename).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(filename, () => filename).IsValidPath();
RemoveReadOnly(filename);
// File.WriteAllText is broken on net core when writing to some CIFS mounts
@@ -333,14 +333,14 @@ namespace NzbDrone.Common.Disk
public void FolderSetLastWriteTime(string path, DateTime dateTime)
{
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(path, () => path).IsValidPath();
_fileSystem.Directory.SetLastWriteTimeUtc(path, dateTime);
}
public void FileSetLastWriteTime(string path, DateTime dateTime)
{
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(path, () => path).IsValidPath();
_fileSystem.File.SetLastWriteTime(path, dateTime);
}
@@ -362,14 +362,14 @@ namespace NzbDrone.Common.Disk
public string GetPathRoot(string path)
{
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(path, () => path).IsValidPath();
return Path.GetPathRoot(path);
}
public string GetParentFolder(string path)
{
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(path, () => path).IsValidPath();
var parent = _fileSystem.Directory.GetParent(path.TrimEnd(Path.DirectorySeparatorChar));
@@ -402,7 +402,7 @@ namespace NzbDrone.Common.Disk
public void EmptyFolder(string path)
{
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(path, () => path).IsValidPath();
foreach (var file in GetFiles(path, SearchOption.TopDirectoryOnly))
{
@@ -473,11 +473,12 @@ namespace NzbDrone.Common.Disk
return mounts.Where(drive => drive.RootDirectory.PathEquals(path) ||
drive.RootDirectory.IsParentPath(path))
.MaxBy(drive => drive.RootDirectory.Length);
.OrderByDescending(drive => drive.RootDirectory.Length)
.FirstOrDefault();
}
catch (Exception ex)
{
Logger.Debug(ex, $"Failed to get mount for path {path}");
Logger.Debug(ex, string.Format("Failed to get mount for path {0}", path));
return null;
}
}
@@ -491,7 +492,7 @@ namespace NzbDrone.Common.Disk
public List<IDirectoryInfo> GetDirectoryInfos(string path)
{
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(path, () => path).IsValidPath();
var di = _fileSystem.DirectoryInfo.FromDirectoryName(path);
@@ -500,13 +501,13 @@ namespace NzbDrone.Common.Disk
public IDirectoryInfo GetDirectoryInfo(string path)
{
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(path, () => path).IsValidPath();
return _fileSystem.DirectoryInfo.FromDirectoryName(path);
}
public List<IFileInfo> GetFileInfos(string path, SearchOption searchOption = SearchOption.TopDirectoryOnly)
{
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(path, () => path).IsValidPath();
var di = _fileSystem.DirectoryInfo.FromDirectoryName(path);
@@ -515,7 +516,7 @@ namespace NzbDrone.Common.Disk
public IFileInfo GetFileInfo(string path)
{
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(path, () => path).IsValidPath();
return _fileSystem.FileInfo.FromFileName(path);
}

View File

@@ -44,8 +44,8 @@ namespace NzbDrone.Common.Disk
public TransferMode TransferFolder(string sourcePath, string targetPath, TransferMode mode)
{
Ensure.That(sourcePath, () => sourcePath).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(targetPath, () => targetPath).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
Ensure.That(targetPath, () => targetPath).IsValidPath();
sourcePath = ResolveRealParentPath(sourcePath);
targetPath = ResolveRealParentPath(targetPath);
@@ -141,8 +141,8 @@ namespace NzbDrone.Common.Disk
{
var filesCopied = 0;
Ensure.That(sourcePath, () => sourcePath).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(targetPath, () => targetPath).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
Ensure.That(targetPath, () => targetPath).IsValidPath();
sourcePath = ResolveRealParentPath(sourcePath);
targetPath = ResolveRealParentPath(targetPath);
@@ -256,8 +256,8 @@ namespace NzbDrone.Common.Disk
public TransferMode TransferFile(string sourcePath, string targetPath, TransferMode mode, bool overwrite = false)
{
Ensure.That(sourcePath, () => sourcePath).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(targetPath, () => targetPath).IsValidPath(PathValidationType.CurrentOs);
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
Ensure.That(targetPath, () => targetPath).IsValidPath();
sourcePath = ResolveRealParentPath(sourcePath);
targetPath = ResolveRealParentPath(targetPath);

View File

@@ -71,7 +71,7 @@ namespace NzbDrone.Common.Disk
if (
allowFoldersWithoutTrailingSlashes &&
query.IsPathValid(PathValidationType.CurrentOs) &&
query.IsPathValid() &&
_diskProvider.FolderExists(query))
{
return GetResult(query, includeFiles);

View File

@@ -2,6 +2,8 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Security.AccessControl;
using System.Security.Principal;
namespace NzbDrone.Common.Disk
{

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Disk
@@ -161,7 +162,7 @@ namespace NzbDrone.Common.Disk
}
}
public bool IsValid => _path.IsPathValid(PathValidationType.CurrentOs);
public bool IsValid => _path.IsPathValid();
private int GetFileNameIndex()
{

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