mirror of
https://github.com/Readarr/Readarr.git
synced 2026-04-18 21:34:28 -04:00
Compare commits
160 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fc402743aa | |||
| b9d53ed732 | |||
| d248747635 | |||
| d70224c811 | |||
| acdf8c8aa8 | |||
| 3ed41554ce | |||
| ce808c6d7b | |||
| 63b1b56a4f | |||
| a5647bedc8 | |||
| fe659bb79d | |||
| 9918535509 | |||
| f9a6db40b8 | |||
| 6273d69ed6 | |||
| 7012380e95 | |||
| b001ecd698 | |||
| e28becdda4 | |||
| eae06695e8 | |||
| 54a9af2ced | |||
| c9b55266fc | |||
| 05b64406a4 | |||
| 1f37c5387b | |||
| 4a6c7042fe | |||
| d7305b9753 | |||
| bd56643eaa | |||
| 44e6de2e23 | |||
| b209d047fa | |||
| fd5ab27df6 | |||
| 4a89befd79 | |||
| 1a30293c33 | |||
| f5c2a6bf51 | |||
| f3d90fdaf1 | |||
| 04c5671a0a | |||
| 22cc88c5e7 | |||
| ca0c95a2d2 | |||
| 419f790d66 | |||
| 9fe08429bc | |||
| 71f4a88ab3 | |||
| 30b283eda3 | |||
| e23d0bbfa1 | |||
| 765a2aa01b | |||
| 64895c3210 | |||
| 03ab84a814 | |||
| b77e5b14e1 | |||
| 75efbd45e1 | |||
| 00cac507ad | |||
| c4850505b0 | |||
| 75213c86a1 | |||
| b8c3a42643 | |||
| 8acb034aa6 | |||
| 889d32552b | |||
| adc5f4db97 | |||
| 9d08050f96 | |||
| f8cffbb4cf | |||
| 14aeb66142 | |||
| 37e8e11e31 | |||
| bdb2f14936 | |||
| a97af657be | |||
| 301127e6dc | |||
| 1f95bcae4e | |||
| 29118cda45 | |||
| 09beaa939d | |||
| 2107624f1c | |||
| c1c2076e5c | |||
| c31a797bd8 | |||
| ebb2b4eca3 | |||
| 3ec5d9b9fe | |||
| 1ad84a7c44 | |||
| 9d67c18254 | |||
| 2e39c7340c | |||
| f75add984f | |||
| f0f6c3eb35 | |||
| d94f866aeb | |||
| 618f07d138 | |||
| 3db33c988a | |||
| 28e38b7f17 | |||
| ca403e6f31 | |||
| 51351dee1d | |||
| 2081f2e321 | |||
| c10a32534c | |||
| 0e415c6ce3 | |||
| a8eb674071 | |||
| 7f8a1cf849 | |||
| a3c0d10240 | |||
| 3ddeaaefe2 | |||
| 6d99de4fe0 | |||
| 8b36a5ce92 | |||
| 1202a43466 | |||
| 82bc2d1aa4 | |||
| ed9af393b7 | |||
| 0799cfc885 | |||
| 331d0c9a9c | |||
| 03c93c9c84 | |||
| 60f6ed030b | |||
| cc70d61735 | |||
| a7b965100d | |||
| 8901118aef | |||
| 99c17d7698 | |||
| b84e83b082 | |||
| 4249f5324a | |||
| 9e1630e9a4 | |||
| 68b2773913 | |||
| ad446b358e | |||
| 423a8ecbe1 | |||
| 29a12aa3b0 | |||
| 695781dde5 | |||
| 4e8ddd3018 | |||
| eb67231a45 | |||
| 3d3a458828 | |||
| a11930a03f | |||
| abaf39d67e | |||
| 894a5943e4 | |||
| be26647afb | |||
| b319a4bce7 | |||
| f03fd7e95e | |||
| 7f25a3c4b1 | |||
| e3247dc505 | |||
| 3677fd6d34 | |||
| 4f6901b1ff | |||
| ce820f6f73 | |||
| 53e6cb24b7 | |||
| 7c1ca8acc1 | |||
| 5e9e578101 | |||
| 156407c541 | |||
| 1ef6c60318 | |||
| 73b3b1848b | |||
| 33fbd95707 | |||
| 704635f758 | |||
| 263e807de2 | |||
| 9ac9bd25c1 | |||
| 4ead5186ae | |||
| dea797c375 | |||
| 58ba24762b | |||
| fbd7b4fe33 | |||
| fee7fbbff6 | |||
| 18253a298e | |||
| 22f92150c3 | |||
| 4d7a762ee8 | |||
| b11517e2ac | |||
| d5af254f47 | |||
| f09da06f80 | |||
| d3443510b4 | |||
| d73eb1b5f9 | |||
| 39778a95bf | |||
| 9fccca1154 | |||
| e165663616 | |||
| b49d2312ab | |||
| 52221c7cf4 | |||
| ad7b110a0b | |||
| b04b483f86 | |||
| b79941e0a1 | |||
| 84d47b1f23 | |||
| 17df4d47fb | |||
| b9f89dddc9 | |||
| e3fc469cd3 | |||
| 4304685a65 | |||
| 7d77b1fbe5 | |||
| 1989174801 | |||
| ac4ae9bb4d | |||
| f399d27470 | |||
| c5fd2e3aa0 |
+1
-2
@@ -3,8 +3,7 @@
|
|||||||
|
|
||||||
# Explicitly set bash scripts to have unix endings
|
# Explicitly set bash scripts to have unix endings
|
||||||
*.sh text eol=lf
|
*.sh text eol=lf
|
||||||
distribution/debian/* text eol=lf
|
distribution/osx/Readarr text eol=lf
|
||||||
macOS/Readarr text eol=lf
|
|
||||||
|
|
||||||
# Custom for Visual Studio
|
# Custom for Visual Studio
|
||||||
*.cs diff=csharp
|
*.cs diff=csharp
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: Bug Report
|
name: Bug Report
|
||||||
description: 'Report a new bug, if you are not 100% certain this is a bug please go to our Reddit or Discord first'
|
description: 'Report a new bug, if you are not 100% certain this is a bug please go to our Discord first'
|
||||||
labels: ['Type: Bug', 'Status: Needs Triage']
|
labels: ['Type: Bug', 'Status: Needs Triage']
|
||||||
body:
|
body:
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
|
|||||||
@@ -3,6 +3,3 @@ contact_links:
|
|||||||
- name: Support via Discord
|
- name: Support via Discord
|
||||||
url: https://readarr.com/discord
|
url: https://readarr.com/discord
|
||||||
about: Chat with users and devs on support and setup related topics.
|
about: Chat with users and devs on support and setup related topics.
|
||||||
- name: Support via Reddit
|
|
||||||
url: https://reddit.com/r/Readarr
|
|
||||||
about: Discuss and search thru support topics.
|
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ jobs:
|
|||||||
issue-comment: >
|
issue-comment: >
|
||||||
:wave: @{issue-author}, we use the issue tracker exclusively
|
:wave: @{issue-author}, we use the issue tracker exclusively
|
||||||
for bug reports and feature requests. However, this issue appears
|
for bug reports and feature requests. However, this issue appears
|
||||||
to be a support request. Please hop over onto our [Discord](https://readarr.com/discord)
|
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
|
close-issue: true
|
||||||
lock-issue: false
|
lock-issue: false
|
||||||
- uses: dessant/support-requests@v3
|
- uses: dessant/support-requests@v3
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ Note that only one type of a given book is supported. If you want both an audiob
|
|||||||
|
|
||||||
[](https://wiki.servarr.com/readarr)
|
[](https://wiki.servarr.com/readarr)
|
||||||
[](https://readarr.com/discord)
|
[](https://readarr.com/discord)
|
||||||
[](https://www.reddit.com/r/readarr)
|
|
||||||
|
|
||||||
Note: GitHub Issues are for Bugs and Feature Requests Only
|
Note: GitHub Issues are for Bugs and Feature Requests Only
|
||||||
|
|
||||||
|
|||||||
+294
-141
@@ -9,13 +9,13 @@ variables:
|
|||||||
testsFolder: './_tests'
|
testsFolder: './_tests'
|
||||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||||
majorVersion: '0.2.4'
|
majorVersion: '0.3.8'
|
||||||
minorVersion: $[counter('minorVersion', 1)]
|
minorVersion: $[counter('minorVersion', 1)]
|
||||||
readarrVersion: '$(majorVersion).$(minorVersion)'
|
readarrVersion: '$(majorVersion).$(minorVersion)'
|
||||||
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
|
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
|
||||||
sentryOrg: 'servarr'
|
sentryOrg: 'servarr'
|
||||||
sentryUrl: 'https://sentry.servarr.com'
|
sentryUrl: 'https://sentry.servarr.com'
|
||||||
dotnetVersion: '6.0.408'
|
dotnetVersion: '6.0.413'
|
||||||
nodeVersion: '16.X'
|
nodeVersion: '16.X'
|
||||||
innoVersion: '6.2.0'
|
innoVersion: '6.2.0'
|
||||||
windowsImage: 'windows-2022'
|
windowsImage: 'windows-2022'
|
||||||
@@ -27,6 +27,10 @@ trigger:
|
|||||||
include:
|
include:
|
||||||
- develop
|
- develop
|
||||||
- master
|
- master
|
||||||
|
paths:
|
||||||
|
exclude:
|
||||||
|
- .github
|
||||||
|
- src/Readarr.Api.*/openapi.json
|
||||||
|
|
||||||
pr:
|
pr:
|
||||||
branches:
|
branches:
|
||||||
@@ -34,82 +38,37 @@ pr:
|
|||||||
- develop
|
- develop
|
||||||
paths:
|
paths:
|
||||||
exclude:
|
exclude:
|
||||||
|
- .github
|
||||||
- src/NzbDrone.Core/Localization/Core
|
- src/NzbDrone.Core/Localization/Core
|
||||||
|
- src/Readarr.Api.*/openapi.json
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
|
- stage: Setup
|
||||||
- stage: Build_Backend_Windows
|
displayName: Setup
|
||||||
displayName: Build Backend
|
|
||||||
dependsOn: []
|
|
||||||
jobs:
|
jobs:
|
||||||
- job: Backend
|
- job:
|
||||||
strategy:
|
displayName: Build Variables
|
||||||
matrix:
|
|
||||||
Windows:
|
|
||||||
osName: 'Windows'
|
|
||||||
imageName: ${{ variables.windowsImage }}
|
|
||||||
enableAnalysis: 'false'
|
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: $(imageName)
|
vmImage: ${{ variables.linuxImage }}
|
||||||
variables:
|
|
||||||
# Disable stylecop here - linting errors get caught by the analyze task
|
|
||||||
EnableAnalyzers: $(enableAnalysis)
|
|
||||||
steps:
|
steps:
|
||||||
# Set the build name properly. The 'name' property won't recursively expand so hack here:
|
# Set the build name properly. The 'name' property won't recursively expand so hack here:
|
||||||
- bash: echo "##vso[build.updatebuildnumber]$READARRVERSION"
|
- bash: echo "##vso[build.updatebuildnumber]$READARRVERSION"
|
||||||
displayName: Set Build Name
|
displayName: Set Build Name
|
||||||
- checkout: self
|
|
||||||
submodules: true
|
|
||||||
fetchDepth: 1
|
|
||||||
- task: UseDotNet@2
|
|
||||||
displayName: 'Install .net core'
|
|
||||||
inputs:
|
|
||||||
version: $(dotnetVersion)
|
|
||||||
- bash: |
|
- bash: |
|
||||||
SDK_PATH="${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}"
|
if [[ $BUILD_REASON == "PullRequest" ]]; then
|
||||||
BUNDLEDVERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
|
git diff origin/develop...HEAD --name-only | grep -E "^(src/|azure-pipelines.yml)"
|
||||||
|
echo $? > not_backend_update
|
||||||
if ! grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
else
|
||||||
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
|
echo 0 > not_backend_update
|
||||||
fi
|
fi
|
||||||
displayName: Extra Platform Support
|
cat not_backend_update
|
||||||
- task: Cache@2
|
displayName: Check for Backend File Changes
|
||||||
inputs:
|
- publish: not_backend_update
|
||||||
key: 'nuget | "$(Agent.OS)" | $(Build.SourcesDirectory)/src/Directory.Packages.props'
|
artifact: not_backend_update
|
||||||
path: $(nugetCacheFolder)
|
displayName: Publish update type
|
||||||
displayName: Cache NuGet packages
|
- stage: Build_Backend
|
||||||
- bash: ./build.sh --backend --enable-bsd
|
displayName: Build Backend
|
||||||
displayName: Build Readarr Backend
|
dependsOn: Setup
|
||||||
env:
|
|
||||||
NUGET_PACKAGES: $(nugetCacheFolder)
|
|
||||||
- powershell: Get-ChildItem _output\net6.0*,_output\*.Update\* -Recurse | Where { $_.Fullname -notlike "*\publish\*" -and $_.attributes -notlike "*directory*" } | Remove-Item
|
|
||||||
displayName: Clean up intermediate output
|
|
||||||
- publish: $(outputFolder)
|
|
||||||
artifact: '$(osName)Backend'
|
|
||||||
displayName: Publish Backend
|
|
||||||
- publish: '$(testsFolder)/net6.0/win-x64/publish'
|
|
||||||
artifact: win-x64-tests
|
|
||||||
displayName: Publish win-x64 Test Package
|
|
||||||
- publish: '$(testsFolder)/net6.0/linux-x64/publish'
|
|
||||||
artifact: linux-x64-tests
|
|
||||||
displayName: Publish linux-x64 Test Package
|
|
||||||
- publish: '$(testsFolder)/net6.0/linux-x86/publish'
|
|
||||||
artifact: linux-x86-tests
|
|
||||||
displayName: Publish linux-x86 Test Package
|
|
||||||
- publish: '$(testsFolder)/net6.0/linux-musl-x64/publish'
|
|
||||||
artifact: linux-musl-x64-tests
|
|
||||||
displayName: Publish linux-musl-x64 Test Package
|
|
||||||
- publish: '$(testsFolder)/net6.0/freebsd-x64/publish'
|
|
||||||
artifact: freebsd-x64-tests
|
|
||||||
displayName: Publish freebsd-x64 Test Package
|
|
||||||
- publish: '$(testsFolder)/net6.0/osx-x64/publish'
|
|
||||||
artifact: osx-x64-tests
|
|
||||||
displayName: Publish osx-x64 Test Package
|
|
||||||
|
|
||||||
- stage: Build_Backend_Other
|
|
||||||
displayName: Build Backend (Other OS)
|
|
||||||
dependsOn: []
|
|
||||||
jobs:
|
jobs:
|
||||||
- job: Backend
|
- job: Backend
|
||||||
strategy:
|
strategy:
|
||||||
@@ -122,6 +81,10 @@ stages:
|
|||||||
osName: 'Mac'
|
osName: 'Mac'
|
||||||
imageName: ${{ variables.macImage }}
|
imageName: ${{ variables.macImage }}
|
||||||
enableAnalysis: 'false'
|
enableAnalysis: 'false'
|
||||||
|
Windows:
|
||||||
|
osName: 'Windows'
|
||||||
|
imageName: ${{ variables.windowsImage }}
|
||||||
|
enableAnalysis: 'false'
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: $(imageName)
|
vmImage: $(imageName)
|
||||||
@@ -137,22 +100,17 @@ stages:
|
|||||||
inputs:
|
inputs:
|
||||||
version: $(dotnetVersion)
|
version: $(dotnetVersion)
|
||||||
- bash: |
|
- bash: |
|
||||||
SDK_PATH="${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}"
|
BUNDLEDVERSIONS=${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}/Microsoft.NETCoreSdk.BundledVersions.props
|
||||||
BUNDLEDVERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
|
echo $BUNDLEDVERSIONS
|
||||||
|
if grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
||||||
if ! grep -q freebsd-x64 $BUNDLEDVERSIONS; then
|
echo "Extra platforms already enabled"
|
||||||
|
else
|
||||||
|
echo "Enabling extra platform support"
|
||||||
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
|
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
|
||||||
fi
|
fi
|
||||||
displayName: Extra Platform Support
|
displayName: Enable Extra Platform Support
|
||||||
- task: Cache@2
|
|
||||||
inputs:
|
|
||||||
key: 'nuget | "$(Agent.OS)" | $(Build.SourcesDirectory)/src/Directory.Packages.props'
|
|
||||||
path: $(nugetCacheFolder)
|
|
||||||
displayName: Cache NuGet packages
|
|
||||||
- bash: ./build.sh --backend --enable-extra-platforms
|
- bash: ./build.sh --backend --enable-extra-platforms
|
||||||
displayName: Build Readarr Backend
|
displayName: Build Readarr Backend
|
||||||
env:
|
|
||||||
NUGET_PACKAGES: $(nugetCacheFolder)
|
|
||||||
- bash: |
|
- bash: |
|
||||||
find ${OUTPUTFOLDER} -type f ! -path "*/publish/*" -exec rm -rf {} \;
|
find ${OUTPUTFOLDER} -type f ! -path "*/publish/*" -exec rm -rf {} \;
|
||||||
find ${OUTPUTFOLDER} -depth -empty -type d -exec rm -r "{}" \;
|
find ${OUTPUTFOLDER} -depth -empty -type d -exec rm -r "{}" \;
|
||||||
@@ -160,10 +118,38 @@ stages:
|
|||||||
find ${TESTSFOLDER} -depth -empty -type d -exec rm -r "{}" \;
|
find ${TESTSFOLDER} -depth -empty -type d -exec rm -r "{}" \;
|
||||||
displayName: Clean up intermediate output
|
displayName: Clean up intermediate output
|
||||||
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
|
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
|
||||||
|
- publish: $(outputFolder)
|
||||||
|
artifact: '$(osName)Backend'
|
||||||
|
displayName: Publish Backend
|
||||||
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
|
- publish: '$(testsFolder)/net6.0/win-x64/publish'
|
||||||
|
artifact: win-x64-tests
|
||||||
|
displayName: Publish win-x64 Test Package
|
||||||
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
|
- publish: '$(testsFolder)/net6.0/linux-x64/publish'
|
||||||
|
artifact: linux-x64-tests
|
||||||
|
displayName: Publish linux-x64 Test Package
|
||||||
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
|
- publish: '$(testsFolder)/net6.0/linux-x86/publish'
|
||||||
|
artifact: linux-x86-tests
|
||||||
|
displayName: Publish linux-x86 Test Package
|
||||||
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
|
- publish: '$(testsFolder)/net6.0/linux-musl-x64/publish'
|
||||||
|
artifact: linux-musl-x64-tests
|
||||||
|
displayName: Publish linux-musl-x64 Test Package
|
||||||
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
|
- publish: '$(testsFolder)/net6.0/freebsd-x64/publish'
|
||||||
|
artifact: freebsd-x64-tests
|
||||||
|
displayName: Publish freebsd-x64 Test Package
|
||||||
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
|
- publish: '$(testsFolder)/net6.0/osx-x64/publish'
|
||||||
|
artifact: osx-x64-tests
|
||||||
|
displayName: Publish osx-x64 Test Package
|
||||||
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
|
|
||||||
- stage: Build_Frontend
|
- stage: Build_Frontend
|
||||||
displayName: Frontend
|
displayName: Frontend
|
||||||
dependsOn: []
|
dependsOn: Setup
|
||||||
jobs:
|
jobs:
|
||||||
- job: Build
|
- job: Build
|
||||||
strategy:
|
strategy:
|
||||||
@@ -192,7 +178,6 @@ stages:
|
|||||||
key: 'yarn | "$(osName)" | yarn.lock'
|
key: 'yarn | "$(osName)" | yarn.lock'
|
||||||
restoreKeys: |
|
restoreKeys: |
|
||||||
yarn | "$(osName)"
|
yarn | "$(osName)"
|
||||||
yarn
|
|
||||||
path: $(yarnCacheFolder)
|
path: $(yarnCacheFolder)
|
||||||
displayName: Cache Yarn packages
|
displayName: Cache Yarn packages
|
||||||
- bash: ./build.sh --frontend
|
- bash: ./build.sh --frontend
|
||||||
@@ -204,10 +189,10 @@ stages:
|
|||||||
artifact: '$(osName)Frontend'
|
artifact: '$(osName)Frontend'
|
||||||
displayName: Publish Frontend
|
displayName: Publish Frontend
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||||
|
|
||||||
- stage: Installer
|
- stage: Installer
|
||||||
dependsOn:
|
dependsOn:
|
||||||
- Build_Backend_Windows
|
- Build_Backend
|
||||||
- Build_Frontend
|
- Build_Frontend
|
||||||
jobs:
|
jobs:
|
||||||
- job: Windows_Installer
|
- job: Windows_Installer
|
||||||
@@ -231,8 +216,8 @@ stages:
|
|||||||
displayName: Fetch Frontend
|
displayName: Fetch Frontend
|
||||||
- bash: |
|
- bash: |
|
||||||
./build.sh --packages --installer
|
./build.sh --packages --installer
|
||||||
cp setup/output/Readarr.*win-x64.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Readarr.${BUILDNAME}.windows-core-x64-installer.exe
|
cp distribution/windows/setup/output/Readarr.*win-x64.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Readarr.${BUILDNAME}.windows-core-x64-installer.exe
|
||||||
cp setup/output/Readarr.*win-x86.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Readarr.${BUILDNAME}.windows-core-x86-installer.exe
|
cp distribution/windows/setup/output/Readarr.*win-x86.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Readarr.${BUILDNAME}.windows-core-x86-installer.exe
|
||||||
displayName: Create Installers
|
displayName: Create Installers
|
||||||
- publish: $(Build.ArtifactStagingDirectory)
|
- publish: $(Build.ArtifactStagingDirectory)
|
||||||
artifact: 'WindowsInstaller'
|
artifact: 'WindowsInstaller'
|
||||||
@@ -240,7 +225,7 @@ stages:
|
|||||||
|
|
||||||
- stage: Packages
|
- stage: Packages
|
||||||
dependsOn:
|
dependsOn:
|
||||||
- Build_Backend_Windows
|
- Build_Backend
|
||||||
- Build_Frontend
|
- Build_Frontend
|
||||||
jobs:
|
jobs:
|
||||||
- job: Other_Packages
|
- job: Other_Packages
|
||||||
@@ -406,14 +391,29 @@ stages:
|
|||||||
SENTRY_AUTH_TOKEN: $(sentryAuthTokenServarr)
|
SENTRY_AUTH_TOKEN: $(sentryAuthTokenServarr)
|
||||||
SENTRY_ORG: $(sentryOrg)
|
SENTRY_ORG: $(sentryOrg)
|
||||||
SENTRY_URL: $(sentryUrl)
|
SENTRY_URL: $(sentryUrl)
|
||||||
|
|
||||||
- stage: Unit_Test
|
- stage: Unit_Test
|
||||||
displayName: Unit Tests
|
displayName: Unit Tests
|
||||||
dependsOn: Build_Backend_Windows
|
dependsOn: Build_Backend
|
||||||
condition: succeeded()
|
|
||||||
jobs:
|
jobs:
|
||||||
|
- job: Prepare
|
||||||
|
pool:
|
||||||
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
steps:
|
||||||
|
- checkout: none
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: 'not_backend_update'
|
||||||
|
targetPath: '.'
|
||||||
|
- bash: echo "##vso[task.setvariable variable=backendNotUpdated;isOutput=true]$(cat not_backend_update)"
|
||||||
|
name: setVar
|
||||||
|
|
||||||
- job: Unit
|
- job: Unit
|
||||||
displayName: Unit Native
|
displayName: Unit Native
|
||||||
|
dependsOn: Prepare
|
||||||
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
workspace:
|
workspace:
|
||||||
clean: all
|
clean: all
|
||||||
|
|
||||||
@@ -479,6 +479,8 @@ stages:
|
|||||||
|
|
||||||
- job: Unit_Docker
|
- job: Unit_Docker
|
||||||
displayName: Unit Docker
|
displayName: Unit Docker
|
||||||
|
dependsOn: Prepare
|
||||||
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
alpine:
|
alpine:
|
||||||
@@ -492,11 +494,11 @@ stages:
|
|||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: ${{ variables.linuxImage }}
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
|
||||||
container: $[ variables['containerImage'] ]
|
container: $[ variables['containerImage'] ]
|
||||||
|
|
||||||
timeoutInMinutes: 10
|
timeoutInMinutes: 10
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: 'Install .NET'
|
displayName: 'Install .NET'
|
||||||
@@ -530,12 +532,14 @@ stages:
|
|||||||
testResultsFiles: '**/TestResult.xml'
|
testResultsFiles: '**/TestResult.xml'
|
||||||
testRunTitle: '$(testName) Unit Tests'
|
testRunTitle: '$(testName) Unit Tests'
|
||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
|
|
||||||
- job: Unit_LinuxCore_Postgres
|
- job: Unit_LinuxCore_Postgres14
|
||||||
displayName: Unit Native LinuxCore with Postgres Database
|
displayName: Unit Native LinuxCore with Postgres14 Database
|
||||||
|
dependsOn: Prepare
|
||||||
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
variables:
|
variables:
|
||||||
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
||||||
artifactName: LinuxCoreTests
|
artifactName: linux-x64-tests
|
||||||
Readarr__Postgres__Host: 'localhost'
|
Readarr__Postgres__Host: 'localhost'
|
||||||
Readarr__Postgres__Port: '5432'
|
Readarr__Postgres__Port: '5432'
|
||||||
Readarr__Postgres__User: 'readarr'
|
Readarr__Postgres__User: 'readarr'
|
||||||
@@ -545,7 +549,7 @@ stages:
|
|||||||
vmImage: ${{ variables.linuxImage }}
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
|
||||||
timeoutInMinutes: 10
|
timeoutInMinutes: 10
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: 'Install .net core'
|
displayName: 'Install .net core'
|
||||||
@@ -556,7 +560,7 @@ stages:
|
|||||||
displayName: Download Test Artifact
|
displayName: Download Test Artifact
|
||||||
inputs:
|
inputs:
|
||||||
buildType: 'current'
|
buildType: 'current'
|
||||||
artifactName: 'linux-x64-Tests'
|
artifactName: $(artifactName)
|
||||||
targetPath: $(testsFolder)
|
targetPath: $(testsFolder)
|
||||||
- bash: find ${TESTSFOLDER} -name "Readarr.Test.Dummy" -exec chmod a+x {} \;
|
- bash: find ${TESTSFOLDER} -name "Readarr.Test.Dummy" -exec chmod a+x {} \;
|
||||||
displayName: Make Test Dummy Executable
|
displayName: Make Test Dummy Executable
|
||||||
@@ -579,15 +583,84 @@ stages:
|
|||||||
inputs:
|
inputs:
|
||||||
testResultsFormat: 'NUnit'
|
testResultsFormat: 'NUnit'
|
||||||
testResultsFiles: '**/TestResult.xml'
|
testResultsFiles: '**/TestResult.xml'
|
||||||
testRunTitle: 'LinuxCore Postgres Unit Tests'
|
testRunTitle: 'LinuxCore Postgres14 Unit Tests'
|
||||||
|
failTaskOnFailedTests: true
|
||||||
|
|
||||||
|
- job: Unit_LinuxCore_Postgres15
|
||||||
|
displayName: Unit Native LinuxCore with Postgres15 Database
|
||||||
|
dependsOn: Prepare
|
||||||
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
|
variables:
|
||||||
|
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
||||||
|
artifactName: linux-x64-tests
|
||||||
|
Readarr__Postgres__Host: 'localhost'
|
||||||
|
Readarr__Postgres__Port: '5432'
|
||||||
|
Readarr__Postgres__User: 'readarr'
|
||||||
|
Readarr__Postgres__Password: 'readarr'
|
||||||
|
|
||||||
|
pool:
|
||||||
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
|
||||||
|
timeoutInMinutes: 10
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- task: UseDotNet@2
|
||||||
|
displayName: 'Install .net core'
|
||||||
|
inputs:
|
||||||
|
version: $(dotnetVersion)
|
||||||
|
- checkout: none
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
displayName: Download Test Artifact
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: $(artifactName)
|
||||||
|
targetPath: $(testsFolder)
|
||||||
|
- bash: find ${TESTSFOLDER} -name "Readarr.Test.Dummy" -exec chmod a+x {} \;
|
||||||
|
displayName: Make Test Dummy Executable
|
||||||
|
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
|
||||||
|
- bash: |
|
||||||
|
docker run -d --name=postgres15 \
|
||||||
|
-e POSTGRES_PASSWORD=readarr \
|
||||||
|
-e POSTGRES_USER=readarr \
|
||||||
|
-p 5432:5432/tcp \
|
||||||
|
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
|
||||||
|
postgres:15
|
||||||
|
displayName: Start postgres
|
||||||
|
- bash: |
|
||||||
|
chmod a+x ${TESTSFOLDER}/test.sh
|
||||||
|
ls -lR ${TESTSFOLDER}
|
||||||
|
${TESTSFOLDER}/test.sh Linux Unit Test
|
||||||
|
displayName: Run Tests
|
||||||
|
- task: PublishTestResults@2
|
||||||
|
displayName: Publish Test Results
|
||||||
|
inputs:
|
||||||
|
testResultsFormat: 'NUnit'
|
||||||
|
testResultsFiles: '**/TestResult.xml'
|
||||||
|
testRunTitle: 'LinuxCore Postgres15 Unit Tests'
|
||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
|
|
||||||
- stage: Integration
|
- stage: Integration
|
||||||
displayName: Integration
|
displayName: Integration
|
||||||
dependsOn: Packages
|
dependsOn: Packages
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
- job: Prepare
|
||||||
|
pool:
|
||||||
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
steps:
|
||||||
|
- checkout: none
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: 'not_backend_update'
|
||||||
|
targetPath: '.'
|
||||||
|
- bash: echo "##vso[task.setvariable variable=backendNotUpdated;isOutput=true]$(cat not_backend_update)"
|
||||||
|
name: setVar
|
||||||
|
|
||||||
- job: Integration_Native
|
- job: Integration_Native
|
||||||
displayName: Integration Native
|
displayName: Integration Native
|
||||||
|
dependsOn: Prepare
|
||||||
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
MacCore:
|
MacCore:
|
||||||
@@ -608,7 +681,7 @@ stages:
|
|||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: $(imageName)
|
vmImage: $(imageName)
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: 'Install .net core'
|
displayName: 'Install .net core'
|
||||||
@@ -630,7 +703,7 @@ stages:
|
|||||||
targetPath: $(Build.ArtifactStagingDirectory)
|
targetPath: $(Build.ArtifactStagingDirectory)
|
||||||
- task: ExtractFiles@1
|
- task: ExtractFiles@1
|
||||||
inputs:
|
inputs:
|
||||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||||
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
||||||
displayName: Extract Package
|
displayName: Extract Package
|
||||||
- bash: |
|
- bash: |
|
||||||
@@ -649,8 +722,10 @@ stages:
|
|||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
displayName: Publish Test Results
|
displayName: Publish Test Results
|
||||||
|
|
||||||
- job: Integration_LinuxCore_Postgres
|
- job: Integration_LinuxCore_Postgres14
|
||||||
displayName: Integration Native LinuxCore with Postgres Database
|
displayName: Integration Native LinuxCore with Postgres14 Database
|
||||||
|
dependsOn: Prepare
|
||||||
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
variables:
|
variables:
|
||||||
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
||||||
Readarr__Postgres__Host: 'localhost'
|
Readarr__Postgres__Host: 'localhost'
|
||||||
@@ -682,7 +757,7 @@ stages:
|
|||||||
targetPath: $(Build.ArtifactStagingDirectory)
|
targetPath: $(Build.ArtifactStagingDirectory)
|
||||||
- task: ExtractFiles@1
|
- task: ExtractFiles@1
|
||||||
inputs:
|
inputs:
|
||||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||||
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
||||||
displayName: Extract Package
|
displayName: Extract Package
|
||||||
- bash: |
|
- bash: |
|
||||||
@@ -705,12 +780,77 @@ stages:
|
|||||||
inputs:
|
inputs:
|
||||||
testResultsFormat: 'NUnit'
|
testResultsFormat: 'NUnit'
|
||||||
testResultsFiles: '**/TestResult.xml'
|
testResultsFiles: '**/TestResult.xml'
|
||||||
testRunTitle: 'Integration LinuxCore Postgres Database Integration Tests'
|
testRunTitle: 'Integration LinuxCore Postgres14 Database Integration Tests'
|
||||||
|
failTaskOnFailedTests: true
|
||||||
|
displayName: Publish Test Results
|
||||||
|
|
||||||
|
|
||||||
|
- job: Integration_LinuxCore_Postgres15
|
||||||
|
displayName: Integration Native LinuxCore with Postgres Database
|
||||||
|
dependsOn: Prepare
|
||||||
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
|
variables:
|
||||||
|
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
||||||
|
Readarr__Postgres__Host: 'localhost'
|
||||||
|
Readarr__Postgres__Port: '5432'
|
||||||
|
Readarr__Postgres__User: 'readarr'
|
||||||
|
Readarr__Postgres__Password: 'readarr'
|
||||||
|
|
||||||
|
pool:
|
||||||
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- task: UseDotNet@2
|
||||||
|
displayName: 'Install .net core'
|
||||||
|
inputs:
|
||||||
|
version: $(dotnetVersion)
|
||||||
|
- checkout: none
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
displayName: Download Test Artifact
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: 'linux-x64-tests'
|
||||||
|
targetPath: $(testsFolder)
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
displayName: Download Build Artifact
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: Packages
|
||||||
|
itemPattern: '**/$(pattern)'
|
||||||
|
targetPath: $(Build.ArtifactStagingDirectory)
|
||||||
|
- task: ExtractFiles@1
|
||||||
|
inputs:
|
||||||
|
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||||
|
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
||||||
|
displayName: Extract Package
|
||||||
|
- bash: |
|
||||||
|
mkdir -p ./bin/
|
||||||
|
cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Readarr/. ./bin/
|
||||||
|
displayName: Move Package Contents
|
||||||
|
- bash: |
|
||||||
|
docker run -d --name=postgres15 \
|
||||||
|
-e POSTGRES_PASSWORD=readarr \
|
||||||
|
-e POSTGRES_USER=readarr \
|
||||||
|
-p 5432:5432/tcp \
|
||||||
|
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
|
||||||
|
postgres:15
|
||||||
|
displayName: Start postgres
|
||||||
|
- bash: |
|
||||||
|
chmod a+x ${TESTSFOLDER}/test.sh
|
||||||
|
${TESTSFOLDER}/test.sh Linux Integration Test
|
||||||
|
displayName: Run Integration Tests
|
||||||
|
- task: PublishTestResults@2
|
||||||
|
inputs:
|
||||||
|
testResultsFormat: 'NUnit'
|
||||||
|
testResultsFiles: '**/TestResult.xml'
|
||||||
|
testRunTitle: 'Integration LinuxCore Postgres15 Database Integration Tests'
|
||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
displayName: Publish Test Results
|
displayName: Publish Test Results
|
||||||
|
|
||||||
- job: Integration_FreeBSD
|
- job: Integration_FreeBSD
|
||||||
displayName: Integration Native FreeBSD
|
displayName: Integration Native FreeBSD
|
||||||
|
dependsOn: Prepare
|
||||||
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
workspace:
|
workspace:
|
||||||
clean: all
|
clean: all
|
||||||
variables:
|
variables:
|
||||||
@@ -755,6 +895,8 @@ stages:
|
|||||||
|
|
||||||
- job: Integration_Docker
|
- job: Integration_Docker
|
||||||
displayName: Integration Docker
|
displayName: Integration Docker
|
||||||
|
dependsOn: Prepare
|
||||||
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
alpine:
|
alpine:
|
||||||
@@ -773,7 +915,7 @@ stages:
|
|||||||
container: $[ variables['containerImage'] ]
|
container: $[ variables['containerImage'] ]
|
||||||
|
|
||||||
timeoutInMinutes: 15
|
timeoutInMinutes: 15
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: 'Install .NET'
|
displayName: 'Install .NET'
|
||||||
@@ -801,7 +943,7 @@ stages:
|
|||||||
targetPath: $(Build.ArtifactStagingDirectory)
|
targetPath: $(Build.ArtifactStagingDirectory)
|
||||||
- task: ExtractFiles@1
|
- task: ExtractFiles@1
|
||||||
inputs:
|
inputs:
|
||||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||||
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
||||||
displayName: Extract Package
|
displayName: Extract Package
|
||||||
- bash: |
|
- bash: |
|
||||||
@@ -823,7 +965,7 @@ stages:
|
|||||||
- stage: Automation
|
- stage: Automation
|
||||||
displayName: Automation
|
displayName: Automation
|
||||||
dependsOn: Packages
|
dependsOn: Packages
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
- job: Automation
|
- job: Automation
|
||||||
strategy:
|
strategy:
|
||||||
@@ -833,20 +975,23 @@ stages:
|
|||||||
artifactName: 'linux-x64'
|
artifactName: 'linux-x64'
|
||||||
imageName: ${{ variables.linuxImage }}
|
imageName: ${{ variables.linuxImage }}
|
||||||
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
pattern: 'Readarr.*.linux-core-x64.tar.gz'
|
||||||
|
failBuild: true
|
||||||
Mac:
|
Mac:
|
||||||
osName: 'Mac'
|
osName: 'Mac'
|
||||||
artifactName: 'osx-x64'
|
artifactName: 'osx-x64'
|
||||||
imageName: ${{ variables.macImage }}
|
imageName: ${{ variables.macImage }}
|
||||||
pattern: 'Readarr.*.osx-core-x64.tar.gz'
|
pattern: 'Readarr.*.osx-core-x64.tar.gz'
|
||||||
|
failBuild: true
|
||||||
Windows:
|
Windows:
|
||||||
osName: 'Windows'
|
osName: 'Windows'
|
||||||
artifactName: 'win-x64'
|
artifactName: 'win-x64'
|
||||||
imageName: ${{ variables.windowsImage }}
|
imageName: ${{ variables.windowsImage }}
|
||||||
pattern: 'Readarr.*.windows-core-x64.zip'
|
pattern: 'Readarr.*.windows-core-x64.zip'
|
||||||
|
failBuild: true
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: $(imageName)
|
vmImage: $(imageName)
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: 'Install .net core'
|
displayName: 'Install .net core'
|
||||||
@@ -868,7 +1013,7 @@ stages:
|
|||||||
targetPath: $(Build.ArtifactStagingDirectory)
|
targetPath: $(Build.ArtifactStagingDirectory)
|
||||||
- task: ExtractFiles@1
|
- task: ExtractFiles@1
|
||||||
inputs:
|
inputs:
|
||||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||||
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
||||||
displayName: Extract Package
|
displayName: Extract Package
|
||||||
- bash: |
|
- bash: |
|
||||||
@@ -888,20 +1033,35 @@ stages:
|
|||||||
TargetFolder: '$(Build.ArtifactStagingDirectory)/screenshots'
|
TargetFolder: '$(Build.ArtifactStagingDirectory)/screenshots'
|
||||||
- publish: $(Build.ArtifactStagingDirectory)/screenshots
|
- publish: $(Build.ArtifactStagingDirectory)/screenshots
|
||||||
artifact: '$(osName)AutomationScreenshots'
|
artifact: '$(osName)AutomationScreenshots'
|
||||||
condition: and(succeeded(), eq(variables['System.JobAttempt'], '1'))
|
|
||||||
displayName: Publish Screenshot Bundle
|
displayName: Publish Screenshot Bundle
|
||||||
|
condition: and(succeeded(), eq(variables['System.JobAttempt'], '1'))
|
||||||
- task: PublishTestResults@2
|
- task: PublishTestResults@2
|
||||||
inputs:
|
inputs:
|
||||||
testResultsFormat: 'NUnit'
|
testResultsFormat: 'NUnit'
|
||||||
testResultsFiles: '**/TestResult.xml'
|
testResultsFiles: '**/TestResult.xml'
|
||||||
testRunTitle: '$(osName) Automation Tests'
|
testRunTitle: '$(osName) Automation Tests'
|
||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: $(failBuild)
|
||||||
displayName: Publish Test Results
|
displayName: Publish Test Results
|
||||||
|
|
||||||
- stage: Analyze
|
- stage: Analyze
|
||||||
dependsOn: []
|
dependsOn:
|
||||||
|
- Setup
|
||||||
displayName: Analyze
|
displayName: Analyze
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
- job: Prepare
|
||||||
|
pool:
|
||||||
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
steps:
|
||||||
|
- checkout: none
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: 'not_backend_update'
|
||||||
|
targetPath: '.'
|
||||||
|
- bash: echo "##vso[task.setvariable variable=backendNotUpdated;isOutput=true]$(cat not_backend_update)"
|
||||||
|
name: setVar
|
||||||
|
|
||||||
- job: Lint_Frontend
|
- job: Lint_Frontend
|
||||||
displayName: Lint Frontend
|
displayName: Lint Frontend
|
||||||
strategy:
|
strategy:
|
||||||
@@ -927,7 +1087,6 @@ stages:
|
|||||||
key: 'yarn | "$(osName)" | yarn.lock'
|
key: 'yarn | "$(osName)" | yarn.lock'
|
||||||
restoreKeys: |
|
restoreKeys: |
|
||||||
yarn | "$(osName)"
|
yarn | "$(osName)"
|
||||||
yarn
|
|
||||||
path: $(yarnCacheFolder)
|
path: $(yarnCacheFolder)
|
||||||
displayName: Cache Yarn packages
|
displayName: Cache Yarn packages
|
||||||
- bash: ./build.sh --lint
|
- bash: ./build.sh --lint
|
||||||
@@ -956,11 +1115,16 @@ stages:
|
|||||||
cliProjectVersion: '$(readarrVersion)'
|
cliProjectVersion: '$(readarrVersion)'
|
||||||
cliSources: './frontend'
|
cliSources: './frontend'
|
||||||
- task: SonarCloudAnalyze@1
|
- task: SonarCloudAnalyze@1
|
||||||
|
|
||||||
- job: Api_Docs
|
- job: Api_Docs
|
||||||
displayName: API Docs
|
displayName: API Docs
|
||||||
|
dependsOn: Prepare
|
||||||
condition: |
|
condition: |
|
||||||
and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop'))
|
and
|
||||||
|
(
|
||||||
|
and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop')),
|
||||||
|
and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
|
)
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: ${{ variables.windowsImage }}
|
vmImage: ${{ variables.windowsImage }}
|
||||||
@@ -973,7 +1137,7 @@ stages:
|
|||||||
- checkout: self
|
- checkout: self
|
||||||
submodules: true
|
submodules: true
|
||||||
persistCredentials: true
|
persistCredentials: true
|
||||||
fetchDepth: 1
|
fetchDepth: 1
|
||||||
- bash: ./docs.sh Windows
|
- bash: ./docs.sh Windows
|
||||||
displayName: Create openapi.json
|
displayName: Create openapi.json
|
||||||
- bash: |
|
- bash: |
|
||||||
@@ -981,10 +1145,9 @@ stages:
|
|||||||
git config --global user.name "Servarr"
|
git config --global user.name "Servarr"
|
||||||
git checkout -b api-docs
|
git checkout -b api-docs
|
||||||
git add .
|
git add .
|
||||||
git status
|
if git status | grep -q modified
|
||||||
if git status | grep modified
|
|
||||||
then
|
then
|
||||||
git commit -am 'Automated API Docs update [skip ci]'
|
git commit -am 'Automated API Docs update'
|
||||||
git push -f --set-upstream origin api-docs
|
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"}'
|
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
|
else
|
||||||
@@ -1008,33 +1171,25 @@ stages:
|
|||||||
|
|
||||||
- job: Analyze_Backend
|
- job: Analyze_Backend
|
||||||
displayName: Backend
|
displayName: Backend
|
||||||
|
dependsOn: Prepare
|
||||||
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
disable.coverage.autogenerate: 'true'
|
disable.coverage.autogenerate: 'true'
|
||||||
|
EnableAnalyzers: 'false'
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: ${{ variables.linuxImage }}
|
vmImage: ${{ variables.windowsImage }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: 'Install .net core 2.1'
|
displayName: 'Install .net core'
|
||||||
inputs:
|
|
||||||
version: 2.1.815
|
|
||||||
- task: UseDotNet@2
|
|
||||||
displayName: 'Install .net core 3.1'
|
|
||||||
inputs:
|
|
||||||
version: 3.1.413
|
|
||||||
- task: UseDotNet@2
|
|
||||||
displayName: 'Install .net core 5.0'
|
|
||||||
inputs:
|
inputs:
|
||||||
version: $(dotnetVersion)
|
version: $(dotnetVersion)
|
||||||
- checkout: self # Need history for Sonar analysis
|
- checkout: self # Need history for Sonar analysis
|
||||||
submodules: true
|
submodules: true
|
||||||
- task: Cache@2
|
- powershell: Set-Service SCardSvr -StartupType Manual
|
||||||
inputs:
|
displayName: Enable Windows Test Service
|
||||||
key: 'nuget | "$(Agent.OS)" | $(Build.SourcesDirectory)/src/Directory.Packages.props'
|
|
||||||
path: $(nugetCacheFolder)
|
|
||||||
displayName: Cache NuGet packages
|
|
||||||
|
|
||||||
- task: SonarCloudPrepare@1
|
- task: SonarCloudPrepare@1
|
||||||
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
||||||
inputs:
|
inputs:
|
||||||
@@ -1045,16 +1200,14 @@ stages:
|
|||||||
projectName: 'Readarr'
|
projectName: 'Readarr'
|
||||||
projectVersion: '$(readarrVersion)'
|
projectVersion: '$(readarrVersion)'
|
||||||
extraProperties: |
|
extraProperties: |
|
||||||
sonar.exclusions=**/obj/**,**/*.dll,**/NzbDrone.Core.Test/Files/**/*,./frontend/**,./src/Libraries/**
|
sonar.exclusions=**/obj/**,**/*.dll,**/NzbDrone.Core.Test/Files/**/*,./frontend/**,**/ExternalModules/**,./src/Libraries/**
|
||||||
sonar.coverage.exclusions=**/Readarr.Api.V1/**/*
|
sonar.coverage.exclusions=**/Readarr.Api.V1/**/*
|
||||||
sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml
|
sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml
|
||||||
sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml
|
sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml
|
||||||
- bash: |
|
- bash: |
|
||||||
./build.sh --backend -f net6.0 -r linux-x64
|
./build.sh --backend -f net6.0 -r win-x64
|
||||||
TEST_DIR=_tests/net6.0/linux-x64/publish/ ./test.sh Linux Unit Coverage
|
TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage
|
||||||
displayName: Coverage Unit Tests
|
displayName: Coverage Unit Tests
|
||||||
env:
|
|
||||||
NUGET_PACKAGES: $(nugetCacheFolder)
|
|
||||||
- task: SonarCloudAnalyze@1
|
- task: SonarCloudAnalyze@1
|
||||||
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
||||||
displayName: Publish SonarCloud Results
|
displayName: Publish SonarCloud Results
|
||||||
@@ -1077,7 +1230,6 @@ stages:
|
|||||||
- Unit_Test
|
- Unit_Test
|
||||||
- Integration
|
- Integration
|
||||||
- Automation
|
- Automation
|
||||||
- Build_Backend_Other
|
|
||||||
condition: eq(variables['system.pullrequest.isfork'], false)
|
condition: eq(variables['system.pullrequest.isfork'], false)
|
||||||
displayName: Build Status Report
|
displayName: Build Status Report
|
||||||
jobs:
|
jobs:
|
||||||
@@ -1101,3 +1253,4 @@ stages:
|
|||||||
DISCORDCHANNELID: $(discordChannelId)
|
DISCORDCHANNELID: $(discordChannelId)
|
||||||
DISCORDWEBHOOKKEY: $(discordWebhookKey)
|
DISCORDWEBHOOKKEY: $(discordWebhookKey)
|
||||||
DISCORDTHREADID: $(discordThreadId)
|
DISCORDTHREADID: $(discordThreadId)
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ UpdateVersionNumber()
|
|||||||
echo "Updating Version Info"
|
echo "Updating Version Info"
|
||||||
sed -i'' -e "s/<AssemblyVersion>[0-9.*]\+<\/AssemblyVersion>/<AssemblyVersion>$READARRVERSION<\/AssemblyVersion>/g" src/Directory.Build.props
|
sed -i'' -e "s/<AssemblyVersion>[0-9.*]\+<\/AssemblyVersion>/<AssemblyVersion>$READARRVERSION<\/AssemblyVersion>/g" src/Directory.Build.props
|
||||||
sed -i'' -e "s/<AssemblyConfiguration>[\$()A-Za-z-]\+<\/AssemblyConfiguration>/<AssemblyConfiguration>${BUILD_SOURCEBRANCHNAME}<\/AssemblyConfiguration>/g" src/Directory.Build.props
|
sed -i'' -e "s/<AssemblyConfiguration>[\$()A-Za-z-]\+<\/AssemblyConfiguration>/<AssemblyConfiguration>${BUILD_SOURCEBRANCHNAME}<\/AssemblyConfiguration>/g" src/Directory.Build.props
|
||||||
sed -i'' -e "s/<string>10.0.0.0<\/string>/<string>$READARRVERSION<\/string>/g" macOS/Readarr.app/Contents/Info.plist
|
sed -i'' -e "s/<string>10.0.0.0<\/string>/<string>$READARRVERSION<\/string>/g" distribution/osx/Readarr.app/Contents/Info.plist
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +183,7 @@ PackageMacOSApp()
|
|||||||
|
|
||||||
rm -rf $folder
|
rm -rf $folder
|
||||||
mkdir -p $folder
|
mkdir -p $folder
|
||||||
cp -r macOS/Readarr.app $folder
|
cp -r distribution/osx/Readarr.app $folder
|
||||||
mkdir -p $folder/Readarr.app/Contents/MacOS
|
mkdir -p $folder/Readarr.app/Contents/MacOS
|
||||||
|
|
||||||
echo "Copying Binaries"
|
echo "Copying Binaries"
|
||||||
@@ -245,7 +245,7 @@ BuildInstaller()
|
|||||||
local framework="$1"
|
local framework="$1"
|
||||||
local runtime="$2"
|
local runtime="$2"
|
||||||
|
|
||||||
./_inno/ISCC.exe setup/readarr.iss "//DFramework=$framework" "//DRuntime=$runtime"
|
./_inno/ISCC.exe distribution/windows/setup/readarr.iss "//DFramework=$framework" "//DRuntime=$runtime"
|
||||||
}
|
}
|
||||||
|
|
||||||
InstallInno()
|
InstallInno()
|
||||||
@@ -391,22 +391,21 @@ then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$FRONTEND" = "YES" ];
|
if [[ "$LINT" = "YES" || "$FRONTEND" = "YES" ]];
|
||||||
then
|
then
|
||||||
YarnInstall
|
YarnInstall
|
||||||
RunWebpack
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$LINT" = "YES" ];
|
if [ "$LINT" = "YES" ];
|
||||||
then
|
then
|
||||||
if [ -z "$FRONTEND" ];
|
|
||||||
then
|
|
||||||
YarnInstall
|
|
||||||
fi
|
|
||||||
|
|
||||||
LintUI
|
LintUI
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "$FRONTEND" = "YES" ];
|
||||||
|
then
|
||||||
|
RunWebpack
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$PACKAGES" = "YES" ];
|
if [ "$PACKAGES" = "YES" ];
|
||||||
then
|
then
|
||||||
UpdateVersionNumber
|
UpdateVersionNumber
|
||||||
|
|||||||
@@ -44,16 +44,16 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
|
|||||||
|
|
||||||
[Tasks]
|
[Tasks]
|
||||||
Name: "desktopIcon"; Description: "{cm:CreateDesktopIcon}"
|
Name: "desktopIcon"; Description: "{cm:CreateDesktopIcon}"
|
||||||
Name: "windowsService"; Description: "Install Windows Service (Starts when the computer starts as the LocalService user, you will need to change the user to access network shares)"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
|
Name: "windowsService"; Description: "Install Windows Service (Starts when the computer starts as the LocalService user, you will need to change the user to access network shares)"; GroupDescription: "Start automatically"; Flags: exclusive
|
||||||
Name: "startupShortcut"; Description: "Create shortcut in Startup folder (Starts when you log into Windows)"; GroupDescription: "Start automatically"; Flags: exclusive
|
Name: "startupShortcut"; Description: "Create shortcut in Startup folder (Starts when you log into Windows)"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
|
||||||
Name: "none"; Description: "Do not start automatically"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
|
Name: "none"; Description: "Do not start automatically"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
|
||||||
|
|
||||||
[Dirs]
|
[Dirs]
|
||||||
Name: "{app}"; Permissions: users-modify
|
Name: "{app}"; Permissions: users-modify
|
||||||
|
|
||||||
[Files]
|
[Files]
|
||||||
Source: "..\_artifacts\{#Runtime}\{#Framework}\Readarr\Readarr.exe"; DestDir: "{app}\bin"; Flags: ignoreversion
|
Source: "..\..\..\_artifacts\{#Runtime}\{#Framework}\Readarr\Readarr.exe"; DestDir: "{app}\bin"; Flags: ignoreversion
|
||||||
Source: "..\_artifacts\{#Runtime}\{#Framework}\Readarr\*"; Excludes: "Readarr.Update"; DestDir: "{app}\bin"; Flags: ignoreversion recursesubdirs createallsubdirs
|
Source: "..\..\..\_artifacts\{#Runtime}\{#Framework}\Readarr\*"; Excludes: "Readarr.Update"; DestDir: "{app}\bin"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||||
|
|
||||||
[Icons]
|
[Icons]
|
||||||
@@ -72,12 +72,13 @@ Filename: "{app}\bin\Readarr.exe"; Description: "Open Readarr Web UI"; Flags: po
|
|||||||
Filename: "{app}\bin\Readarr.exe"; Description: "Start Readarr"; Flags: postinstall skipifsilent nowait; Tasks: startupShortcut none;
|
Filename: "{app}\bin\Readarr.exe"; Description: "Start Readarr"; Flags: postinstall skipifsilent nowait; Tasks: startupShortcut none;
|
||||||
|
|
||||||
[UninstallRun]
|
[UninstallRun]
|
||||||
Filename: "{app}\bin\Readarr.Console.exe"; Parameters: "/u"; Flags: waituntilterminated skipifdoesntexist
|
Filename: "{app}\bin\readarr.console.exe"; Parameters: "/u"; Flags: waituntilterminated skipifdoesntexist
|
||||||
|
|
||||||
[Code]
|
[Code]
|
||||||
function PrepareToInstall(var NeedsRestart: Boolean): String;
|
function PrepareToInstall(var NeedsRestart: Boolean): String;
|
||||||
var
|
var
|
||||||
ResultCode: Integer;
|
ResultCode: Integer;
|
||||||
begin
|
begin
|
||||||
Exec(ExpandConstant('{commonappdata}\Readarr\bin\Readarr.Console.exe'), '/u', '', 0, ewWaitUntilTerminated, ResultCode)
|
Exec('net', 'stop readarr', '', 0, ewWaitUntilTerminated, ResultCode)
|
||||||
|
Exec('sc', 'delete readarr', '', 0, ewWaitUntilTerminated, ResultCode)
|
||||||
end;
|
end;
|
||||||
@@ -4,14 +4,14 @@ module.exports = {
|
|||||||
plugins: [
|
plugins: [
|
||||||
// Stage 1
|
// Stage 1
|
||||||
'@babel/plugin-proposal-export-default-from',
|
'@babel/plugin-proposal-export-default-from',
|
||||||
['@babel/plugin-proposal-optional-chaining', { loose }],
|
['@babel/plugin-transform-optional-chaining', { loose }],
|
||||||
['@babel/plugin-proposal-nullish-coalescing-operator', { loose }],
|
['@babel/plugin-transform-nullish-coalescing-operator', { loose }],
|
||||||
|
|
||||||
// Stage 2
|
// Stage 2
|
||||||
'@babel/plugin-proposal-export-namespace-from',
|
'@babel/plugin-transform-export-namespace-from',
|
||||||
|
|
||||||
// Stage 3
|
// Stage 3
|
||||||
['@babel/plugin-proposal-class-properties', { loose }],
|
['@babel/plugin-transform-class-properties', { loose }],
|
||||||
'@babel/plugin-syntax-dynamic-import'
|
'@babel/plugin-syntax-dynamic-import'
|
||||||
],
|
],
|
||||||
env: {
|
env: {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ module.exports = (env) => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
entry: {
|
entry: {
|
||||||
index: 'index.js'
|
index: 'index.ts'
|
||||||
},
|
},
|
||||||
|
|
||||||
resolve: {
|
resolve: {
|
||||||
@@ -91,13 +91,15 @@ module.exports = (env) => {
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
new MiniCssExtractPlugin({
|
new MiniCssExtractPlugin({
|
||||||
filename: 'Content/styles.css'
|
filename: 'Content/styles.css',
|
||||||
|
chunkFilename: 'Content/[id]-[chunkhash].css'
|
||||||
}),
|
}),
|
||||||
|
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
template: 'frontend/src/index.ejs',
|
template: 'frontend/src/index.ejs',
|
||||||
filename: 'index.html',
|
filename: 'index.html',
|
||||||
publicPath: '/'
|
publicPath: '/',
|
||||||
|
inject: false
|
||||||
}),
|
}),
|
||||||
|
|
||||||
new FileManagerPlugin({
|
new FileManagerPlugin({
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import Link from 'Components/Link/Link';
|
|||||||
import { icons } from 'Helpers/Props';
|
import { icons } from 'Helpers/Props';
|
||||||
import formatDateTime from 'Utilities/Date/formatDateTime';
|
import formatDateTime from 'Utilities/Date/formatDateTime';
|
||||||
import formatAge from 'Utilities/Number/formatAge';
|
import formatAge from 'Utilities/Number/formatAge';
|
||||||
import formatPreferredWordScore from 'Utilities/Number/formatPreferredWordScore';
|
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import styles from './HistoryDetails.css';
|
import styles from './HistoryDetails.css';
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ function HistoryDetails(props) {
|
|||||||
customFormatScore && customFormatScore !== '0' ?
|
customFormatScore && customFormatScore !== '0' ?
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title={translate('CustomFormatScore')}
|
title={translate('CustomFormatScore')}
|
||||||
data={formatPreferredWordScore(customFormatScore)}
|
data={formatCustomFormatScore(customFormatScore)}
|
||||||
/> :
|
/> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
@@ -225,7 +225,7 @@ function HistoryDetails(props) {
|
|||||||
customFormatScore && customFormatScore !== '0' ?
|
customFormatScore && customFormatScore !== '0' ?
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title={translate('CustomFormatScore')}
|
title={translate('CustomFormatScore')}
|
||||||
data={formatPreferredWordScore(customFormatScore)}
|
data={formatCustomFormatScore(customFormatScore)}
|
||||||
/> :
|
/> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
@@ -271,7 +271,7 @@ function HistoryDetails(props) {
|
|||||||
customFormatScore && customFormatScore !== '0' ?
|
customFormatScore && customFormatScore !== '0' ?
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title={translate('CustomFormatScore')}
|
title={translate('CustomFormatScore')}
|
||||||
data={formatPreferredWordScore(customFormatScore)}
|
data={formatCustomFormatScore(customFormatScore)}
|
||||||
/> :
|
/> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
|||||||
import TableRow from 'Components/Table/TableRow';
|
import TableRow from 'Components/Table/TableRow';
|
||||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||||
import { icons, tooltipPositions } from 'Helpers/Props';
|
import { icons, tooltipPositions } from 'Helpers/Props';
|
||||||
import formatPreferredWordScore from 'Utilities/Number/formatPreferredWordScore';
|
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
|
||||||
import HistoryDetailsModal from './Details/HistoryDetailsModal';
|
import HistoryDetailsModal from './Details/HistoryDetailsModal';
|
||||||
import HistoryEventTypeCell from './HistoryEventTypeCell';
|
import HistoryEventTypeCell from './HistoryEventTypeCell';
|
||||||
import styles from './HistoryRow.css';
|
import styles from './HistoryRow.css';
|
||||||
@@ -180,7 +180,7 @@ class HistoryRow extends Component {
|
|||||||
className={styles.customFormatScore}
|
className={styles.customFormatScore}
|
||||||
>
|
>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
anchor={formatPreferredWordScore(
|
anchor={formatCustomFormatScore(
|
||||||
customFormatScore,
|
customFormatScore,
|
||||||
customFormats.length
|
customFormats.length
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import Tooltip from 'Components/Tooltip/Tooltip';
|
|||||||
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
||||||
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
import formatPreferredWordScore from 'Utilities/Number/formatPreferredWordScore';
|
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import QueueStatusCell from './QueueStatusCell';
|
import QueueStatusCell from './QueueStatusCell';
|
||||||
import RemoveQueueItemModal from './RemoveQueueItemModal';
|
import RemoveQueueItemModal from './RemoveQueueItemModal';
|
||||||
@@ -46,14 +46,14 @@ class QueueRow extends Component {
|
|||||||
this.setState({ isRemoveQueueItemModalOpen: true });
|
this.setState({ isRemoveQueueItemModalOpen: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
onRemoveQueueItemModalConfirmed = (blocklist, skipredownload) => {
|
onRemoveQueueItemModalConfirmed = (blocklist, skipRedownload) => {
|
||||||
const {
|
const {
|
||||||
onRemoveQueueItemPress,
|
onRemoveQueueItemPress,
|
||||||
onQueueRowModalOpenOrClose
|
onQueueRowModalOpenOrClose
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
onQueueRowModalOpenOrClose(false);
|
onQueueRowModalOpenOrClose(false);
|
||||||
onRemoveQueueItemPress(blocklist, skipredownload);
|
onRemoveQueueItemPress(blocklist, skipRedownload);
|
||||||
|
|
||||||
this.setState({ isRemoveQueueItemModalOpen: false });
|
this.setState({ isRemoveQueueItemModalOpen: false });
|
||||||
};
|
};
|
||||||
@@ -232,7 +232,7 @@ class QueueRow extends Component {
|
|||||||
className={styles.customFormatScore}
|
className={styles.customFormatScore}
|
||||||
>
|
>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
anchor={formatPreferredWordScore(
|
anchor={formatCustomFormatScore(
|
||||||
customFormatScore,
|
customFormatScore,
|
||||||
customFormats.length
|
customFormats.length
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class RemoveQueueItemModal extends Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
remove: true,
|
remove: true,
|
||||||
blocklist: false,
|
blocklist: false,
|
||||||
skipredownload: false
|
skipRedownload: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ class RemoveQueueItemModal extends Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
remove: true,
|
remove: true,
|
||||||
blocklist: false,
|
blocklist: false,
|
||||||
skipredownload: false
|
skipRedownload: false
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -49,8 +49,8 @@ class RemoveQueueItemModal extends Component {
|
|||||||
this.setState({ blocklist: value });
|
this.setState({ blocklist: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
onSkipReDownloadChange = ({ value }) => {
|
onSkipRedownloadChange = ({ value }) => {
|
||||||
this.setState({ skipredownload: value });
|
this.setState({ skipRedownload: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
onRemoveConfirmed = () => {
|
onRemoveConfirmed = () => {
|
||||||
@@ -76,7 +76,7 @@ class RemoveQueueItemModal extends Component {
|
|||||||
isPending
|
isPending
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const { remove, blocklist, skipredownload } = this.state;
|
const { remove, blocklist, skipRedownload } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@@ -137,10 +137,10 @@ class RemoveQueueItemModal extends Component {
|
|||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
name="skipredownload"
|
name="skipRedownload"
|
||||||
value={skipredownload}
|
value={skipRedownload}
|
||||||
helpText={translate('SkipredownloadHelpText')}
|
helpText={translate('SkipRedownloadHelpText')}
|
||||||
onChange={this.onSkipReDownloadChange}
|
onChange={this.onSkipRedownloadChange}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class RemoveQueueItemsModal extends Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
remove: true,
|
remove: true,
|
||||||
blocklist: false,
|
blocklist: false,
|
||||||
skipredownload: false
|
skipRedownload: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ class RemoveQueueItemsModal extends Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
remove: true,
|
remove: true,
|
||||||
blocklist: false,
|
blocklist: false,
|
||||||
skipredownload: false
|
skipRedownload: false
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -50,8 +50,8 @@ class RemoveQueueItemsModal extends Component {
|
|||||||
this.setState({ blocklist: value });
|
this.setState({ blocklist: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
onSkipReDownloadChange = ({ value }) => {
|
onSkipRedownloadChange = ({ value }) => {
|
||||||
this.setState({ skipredownload: value });
|
this.setState({ skipRedownload: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
onRemoveConfirmed = () => {
|
onRemoveConfirmed = () => {
|
||||||
@@ -77,7 +77,7 @@ class RemoveQueueItemsModal extends Component {
|
|||||||
allPending
|
allPending
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const { remove, blocklist, skipredownload } = this.state;
|
const { remove, blocklist, skipRedownload } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@@ -138,10 +138,10 @@ class RemoveQueueItemsModal extends Component {
|
|||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
name="skipredownload"
|
name="skipRedownload"
|
||||||
value={skipredownload}
|
value={skipRedownload}
|
||||||
helpText={translate('SkipredownloadHelpText')}
|
helpText={translate('SkipRedownloadHelpText')}
|
||||||
onChange={this.onSkipReDownloadChange}
|
onChange={this.onSkipRedownloadChange}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import PageConnector from 'Components/Page/PageConnector';
|
|||||||
import ApplyTheme from './ApplyTheme';
|
import ApplyTheme from './ApplyTheme';
|
||||||
import AppRoutes from './AppRoutes';
|
import AppRoutes from './AppRoutes';
|
||||||
|
|
||||||
function App({ store, history, hasTranslationsError }) {
|
function App({ store, history }) {
|
||||||
return (
|
return (
|
||||||
<DocumentTitle title={window.Readarr.instanceName}>
|
<DocumentTitle title={window.Readarr.instanceName}>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ConnectedRouter history={history}>
|
<ConnectedRouter history={history}>
|
||||||
<ApplyTheme>
|
<ApplyTheme>
|
||||||
<PageConnector hasTranslationsError={hasTranslationsError}>
|
<PageConnector>
|
||||||
<AppRoutes app={App} />
|
<AppRoutes app={App} />
|
||||||
</PageConnector>
|
</PageConnector>
|
||||||
</ApplyTheme>
|
</ApplyTheme>
|
||||||
@@ -25,8 +25,7 @@ function App({ store, history, hasTranslationsError }) {
|
|||||||
|
|
||||||
App.propTypes = {
|
App.propTypes = {
|
||||||
store: PropTypes.object.isRequired,
|
store: PropTypes.object.isRequired,
|
||||||
history: PropTypes.object.isRequired,
|
history: PropTypes.object.isRequired
|
||||||
hasTranslationsError: PropTypes.bool.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
.version {
|
.version {
|
||||||
margin: 0 3px;
|
margin: 0 3px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
font-family: var(--defaultFontFamily);
|
||||||
}
|
}
|
||||||
|
|
||||||
.maintenance {
|
.maintenance {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Button from 'Components/Link/Button';
|
import Button from 'Components/Link/Button';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
|
||||||
import ModalBody from 'Components/Modal/ModalBody';
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
import ModalContent from 'Components/Modal/ModalContent';
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
@@ -64,12 +65,12 @@ function AppUpdatedModalContent(props) {
|
|||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
Readarr Updated
|
{translate('AppUpdated', { appName: 'Readarr' })}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<div>
|
<div>
|
||||||
Version <span className={styles.version}>{version}</span> of Readarr has been installed, in order to get the latest changes you'll need to reload Readarr.
|
<InlineMarkdown data={translate('AppUpdatedVersion', { appName: 'Readarr', version })} blockClassName={styles.version} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -77,16 +78,14 @@ function AppUpdatedModalContent(props) {
|
|||||||
<div>
|
<div>
|
||||||
{
|
{
|
||||||
!update.changes &&
|
!update.changes &&
|
||||||
<div className={styles.maintenance}>
|
<div className={styles.maintenance}>{translate('MaintenanceRelease')}</div>
|
||||||
{translate('MaintenanceRelease')}
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!!update.changes &&
|
!!update.changes &&
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.changes}>
|
<div className={styles.changes}>
|
||||||
What's new?
|
{translate('WhatsNew')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UpdateChanges
|
<UpdateChanges
|
||||||
@@ -113,14 +112,14 @@ function AppUpdatedModalContent(props) {
|
|||||||
<Button
|
<Button
|
||||||
onPress={onSeeChangesPress}
|
onPress={onSeeChangesPress}
|
||||||
>
|
>
|
||||||
Recent Changes
|
{translate('RecentChanges')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
kind={kinds.PRIMARY}
|
kind={kinds.PRIMARY}
|
||||||
onPress={onModalClose}
|
onPress={onModalClose}
|
||||||
>
|
>
|
||||||
Reload
|
{translate('Reload')}
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import ModalContent from 'Components/Modal/ModalContent';
|
|||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
import { kinds } from 'Helpers/Props';
|
import { kinds } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
import styles from './ConnectionLostModal.css';
|
import styles from './ConnectionLostModal.css';
|
||||||
|
|
||||||
function ConnectionLostModal(props) {
|
function ConnectionLostModal(props) {
|
||||||
@@ -22,16 +23,16 @@ function ConnectionLostModal(props) {
|
|||||||
>
|
>
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
Connection Lost
|
{translate('ConnectionLost')}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<div>
|
<div>
|
||||||
Readarr has lost its connection to the backend and will need to be reloaded to restore functionality.
|
{translate('ConnectionLostToBackend', { appName: 'Readarr' })}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.automatic}>
|
<div className={styles.automatic}>
|
||||||
Readarr will try to connect automatically, or you can click reload below.
|
{translate('ConnectionLostReconnect', { appName: 'Readarr' })}
|
||||||
</div>
|
</div>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
@@ -39,7 +40,7 @@ function ConnectionLostModal(props) {
|
|||||||
kind={kinds.PRIMARY}
|
kind={kinds.PRIMARY}
|
||||||
onPress={onModalClose}
|
onPress={onModalClose}
|
||||||
>
|
>
|
||||||
Reload
|
{translate('Reload')}
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|||||||
@@ -7,13 +7,10 @@ function findImage(images, coverType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getUrl(image, coverType, size) {
|
function getUrl(image, coverType, size) {
|
||||||
if (image) {
|
const imageUrl = image?.url;
|
||||||
// Remove protocol
|
|
||||||
let url = image.url;
|
|
||||||
|
|
||||||
url = url.replace(`${coverType}.jpg`, `${coverType}-${size}.jpg`);
|
if (imageUrl) {
|
||||||
|
return imageUrl.replace(`${coverType}.jpg`, `${coverType}-${size}.jpg`);
|
||||||
return url;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,12 +25,7 @@ const defaultFontSize = parseInt(fonts.defaultFontSize);
|
|||||||
const lineHeight = parseFloat(fonts.lineHeight);
|
const lineHeight = parseFloat(fonts.lineHeight);
|
||||||
|
|
||||||
function getFanartUrl(images) {
|
function getFanartUrl(images) {
|
||||||
const fanartImage = images.find((x) => x.coverType === 'fanart');
|
return images.find((x) => x.coverType === 'fanart')?.url;
|
||||||
|
|
||||||
if (fanartImage) {
|
|
||||||
// Remove protocol
|
|
||||||
return fanartImage.url.replace(/^https?:/, '');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class AuthorDetailsHeader extends Component {
|
class AuthorDetailsHeader extends Component {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import MoveAuthorModal from 'Author/MoveAuthor/MoveAuthorModal';
|
import MoveAuthorModal from 'Author/MoveAuthor/MoveAuthorModal';
|
||||||
import MetadataProfileSelectInputConnector from 'Components/Form/MetadataProfileSelectInputConnector';
|
import MetadataProfileSelectInputConnector from 'Components/Form/MetadataProfileSelectInputConnector';
|
||||||
import MonitorNewItemsSelectInput from 'Components/Form/MonitorNewItemsSelectInput';
|
import MonitorNewItemsSelectInput from 'Components/Form/MonitorNewItemsSelectInput';
|
||||||
@@ -9,6 +10,7 @@ import SelectInput from 'Components/Form/SelectInput';
|
|||||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||||
import PageContentFooter from 'Components/Page/PageContentFooter';
|
import PageContentFooter from 'Components/Page/PageContentFooter';
|
||||||
import { kinds } from 'Helpers/Props';
|
import { kinds } from 'Helpers/Props';
|
||||||
|
import { fetchRootFolders } from 'Store/Actions/Settings/rootFolders';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import AuthorEditorFooterLabel from './AuthorEditorFooterLabel';
|
import AuthorEditorFooterLabel from './AuthorEditorFooterLabel';
|
||||||
import DeleteAuthorModal from './Delete/DeleteAuthorModal';
|
import DeleteAuthorModal from './Delete/DeleteAuthorModal';
|
||||||
@@ -17,6 +19,10 @@ import styles from './AuthorEditorFooter.css';
|
|||||||
|
|
||||||
const NO_CHANGE = 'noChange';
|
const NO_CHANGE = 'noChange';
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
dispatchFetchRootFolders: fetchRootFolders
|
||||||
|
};
|
||||||
|
|
||||||
class AuthorEditorFooter extends Component {
|
class AuthorEditorFooter extends Component {
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -39,6 +45,13 @@ class AuthorEditorFooter extends Component {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.dispatchFetchRootFolders();
|
||||||
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
const {
|
const {
|
||||||
isSaving,
|
isSaving,
|
||||||
@@ -341,7 +354,8 @@ AuthorEditorFooter.propTypes = {
|
|||||||
showMetadataProfile: PropTypes.bool.isRequired,
|
showMetadataProfile: PropTypes.bool.isRequired,
|
||||||
onSaveSelected: PropTypes.func.isRequired,
|
onSaveSelected: PropTypes.func.isRequired,
|
||||||
onOrganizeAuthorPress: PropTypes.func.isRequired,
|
onOrganizeAuthorPress: PropTypes.func.isRequired,
|
||||||
onRetagAuthorPress: PropTypes.func.isRequired
|
onRetagAuthorPress: PropTypes.func.isRequired,
|
||||||
|
dispatchFetchRootFolders: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AuthorEditorFooter;
|
export default connect(undefined, mapDispatchToProps)(AuthorEditorFooter);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import AuthorIndex from './AuthorIndex';
|
|||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createAuthorClientSideCollectionItemsSelector('authorIndex'),
|
createAuthorClientSideCollectionItemsSelector('authorIndex'),
|
||||||
createCommandExecutingSelector(commandNames.REFRESH_AUTHOR),
|
createCommandExecutingSelector(commandNames.BULK_REFRESH_AUTHOR),
|
||||||
createCommandExecutingSelector(commandNames.RSS_SYNC),
|
createCommandExecutingSelector(commandNames.RSS_SYNC),
|
||||||
createCommandExecutingSelector(commandNames.RENAME_AUTHOR),
|
createCommandExecutingSelector(commandNames.RENAME_AUTHOR),
|
||||||
createCommandExecutingSelector(commandNames.RETAG_AUTHOR),
|
createCommandExecutingSelector(commandNames.RETAG_AUTHOR),
|
||||||
@@ -24,17 +24,17 @@ function createMapStateToProps() {
|
|||||||
(
|
(
|
||||||
author,
|
author,
|
||||||
isRefreshingAuthor,
|
isRefreshingAuthor,
|
||||||
|
isRssSyncExecuting,
|
||||||
isOrganizingAuthor,
|
isOrganizingAuthor,
|
||||||
isRetaggingAuthor,
|
isRetaggingAuthor,
|
||||||
isRssSyncExecuting,
|
|
||||||
dimensionsState
|
dimensionsState
|
||||||
) => {
|
) => {
|
||||||
return {
|
return {
|
||||||
...author,
|
...author,
|
||||||
isRefreshingAuthor,
|
isRefreshingAuthor,
|
||||||
|
isRssSyncExecuting,
|
||||||
isOrganizingAuthor,
|
isOrganizingAuthor,
|
||||||
isRetaggingAuthor,
|
isRetaggingAuthor,
|
||||||
isRssSyncExecuting,
|
|
||||||
isSmallScreen: dimensionsState.isSmallScreen
|
isSmallScreen: dimensionsState.isSmallScreen
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-5
@@ -14,14 +14,39 @@ import { inputTypes } from 'Helpers/Props';
|
|||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
|
|
||||||
const nameOptions = [
|
const nameOptions = [
|
||||||
{ key: 'firstLast', value: translate('NameFirstLast') },
|
{
|
||||||
{ key: 'lastFirst', value: translate('NameLastFirst') }
|
key: 'firstLast',
|
||||||
|
get value() {
|
||||||
|
return translate('NameFirstLast');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'lastFirst',
|
||||||
|
get value() {
|
||||||
|
return translate('NameLastFirst');
|
||||||
|
}
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const posterSizeOptions = [
|
const posterSizeOptions = [
|
||||||
{ key: 'small', value: 'Small' },
|
{
|
||||||
{ key: 'medium', value: 'Medium' },
|
key: 'small',
|
||||||
{ key: 'large', value: 'Large' }
|
get value() {
|
||||||
|
return translate('Small');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'medium',
|
||||||
|
get value() {
|
||||||
|
return translate('Medium');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'large',
|
||||||
|
get value() {
|
||||||
|
return translate('Large');
|
||||||
|
}
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
class AuthorIndexOverviewOptionsModalContent extends Component {
|
class AuthorIndexOverviewOptionsModalContent extends Component {
|
||||||
|
|||||||
@@ -14,15 +14,45 @@ import { inputTypes } from 'Helpers/Props';
|
|||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
|
|
||||||
const posterSizeOptions = [
|
const posterSizeOptions = [
|
||||||
{ key: 'small', value: 'Small' },
|
{
|
||||||
{ key: 'medium', value: 'Medium' },
|
key: 'small',
|
||||||
{ key: 'large', value: 'Large' }
|
get value() {
|
||||||
|
return translate('Small');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'medium',
|
||||||
|
get value() {
|
||||||
|
return translate('Medium');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'large',
|
||||||
|
get value() {
|
||||||
|
return translate('Large');
|
||||||
|
}
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const nameOptions = [
|
const nameOptions = [
|
||||||
{ key: 'no', value: translate('NoName') },
|
{
|
||||||
{ key: 'firstLast', value: translate('NameFirstLast') },
|
key: 'no',
|
||||||
{ key: 'lastFirst', value: translate('NameLastFirst') }
|
get value() {
|
||||||
|
return translate('NoName');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'firstLast',
|
||||||
|
get value() {
|
||||||
|
return translate('NameFirstLast');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'lastFirst',
|
||||||
|
get value() {
|
||||||
|
return translate('NameLastFirst');
|
||||||
|
}
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
class AuthorIndexPosterOptionsModalContent extends Component {
|
class AuthorIndexPosterOptionsModalContent extends Component {
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ function AuthorIndexProgressBar(props) {
|
|||||||
detailedProgressBar
|
detailedProgressBar
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const progress = bookCount ? bookFileCount / bookCount * 100 : 100;
|
const progress = bookCount ? bookCount / totalBookCount * 100 : 100;
|
||||||
const text = `${bookFileCount} / ${bookCount}`;
|
const text = `${bookCount} / ${totalBookCount}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ class AuthorIndexRow extends Component {
|
|||||||
progress={progress}
|
progress={progress}
|
||||||
kind={getProgressBarKind(status, monitored, progress)}
|
kind={getProgressBarKind(status, monitored, progress)}
|
||||||
showText={true}
|
showText={true}
|
||||||
text={`${bookFileCount} / ${bookCount}`}
|
text={`${bookCount} / ${totalBookCount}`}
|
||||||
title={translate('BookFileCountBookCountTotalTotalBookCountInterp', [bookFileCount, bookCount, totalBookCount])}
|
title={translate('BookFileCountBookCountTotalTotalBookCountInterp', [bookFileCount, bookCount, totalBookCount])}
|
||||||
width={125}
|
width={125}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -7,8 +7,18 @@ import { inputTypes } from 'Helpers/Props';
|
|||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
|
|
||||||
const nameOptions = [
|
const nameOptions = [
|
||||||
{ key: 'firstLast', value: translate('NameFirstLast') },
|
{
|
||||||
{ key: 'lastFirst', value: translate('NameLastFirst') }
|
key: 'firstLast',
|
||||||
|
get value() {
|
||||||
|
return translate('NameFirstLast');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'lastFirst',
|
||||||
|
get value() {
|
||||||
|
return translate('NameLastFirst');
|
||||||
|
}
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
class AuthorIndexTableOptions extends Component {
|
class AuthorIndexTableOptions extends Component {
|
||||||
|
|||||||
@@ -6,4 +6,5 @@
|
|||||||
|
|
||||||
.statusIcon {
|
.statusIcon {
|
||||||
width: 20px !important;
|
width: 20px !important;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class MonitoringOptionsModalContent extends Component {
|
|||||||
const {
|
const {
|
||||||
isSaving,
|
isSaving,
|
||||||
saveError
|
saveError
|
||||||
} = prevProps;
|
} = this.props;
|
||||||
|
|
||||||
if (prevProps.isSaving && !isSaving && !saveError) {
|
if (prevProps.isSaving && !isSaving && !saveError) {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|||||||
@@ -21,12 +21,7 @@ const defaultFontSize = parseInt(fonts.defaultFontSize);
|
|||||||
const lineHeight = parseFloat(fonts.lineHeight);
|
const lineHeight = parseFloat(fonts.lineHeight);
|
||||||
|
|
||||||
function getFanartUrl(images) {
|
function getFanartUrl(images) {
|
||||||
const fanartImage = images.find((x) => x.coverType === 'fanart');
|
return images.find((x) => x.coverType === 'fanart')?.url;
|
||||||
|
|
||||||
if (fanartImage) {
|
|
||||||
// Remove protocol
|
|
||||||
return fanartImage.url.replace(/^https?:/, '');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class BookDetailsHeader extends Component {
|
class BookDetailsHeader extends Component {
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import BookIndex from './BookIndex';
|
|||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createBookClientSideCollectionItemsSelector('bookIndex'),
|
createBookClientSideCollectionItemsSelector('bookIndex'),
|
||||||
createCommandExecutingSelector(commandNames.REFRESH_AUTHOR),
|
createCommandExecutingSelector(commandNames.BULK_REFRESH_AUTHOR),
|
||||||
createCommandExecutingSelector(commandNames.REFRESH_BOOK),
|
createCommandExecutingSelector(commandNames.BULK_REFRESH_BOOK),
|
||||||
createCommandExecutingSelector(commandNames.RSS_SYNC),
|
createCommandExecutingSelector(commandNames.RSS_SYNC),
|
||||||
createCommandExecutingSelector(commandNames.CUTOFF_UNMET_BOOK_SEARCH),
|
createCommandExecutingSelector(commandNames.CUTOFF_UNMET_BOOK_SEARCH),
|
||||||
createCommandExecutingSelector(commandNames.MISSING_BOOK_SEARCH),
|
createCommandExecutingSelector(commandNames.MISSING_BOOK_SEARCH),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Modal from 'Components/Modal/Modal';
|
import Modal from 'Components/Modal/Modal';
|
||||||
|
import { sizes } from 'Helpers/Props';
|
||||||
import BookInteractiveSearchModalContent from './BookInteractiveSearchModalContent';
|
import BookInteractiveSearchModalContent from './BookInteractiveSearchModalContent';
|
||||||
|
|
||||||
function BookInteractiveSearchModal(props) {
|
function BookInteractiveSearchModal(props) {
|
||||||
@@ -14,6 +15,7 @@ function BookInteractiveSearchModal(props) {
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
|
size={sizes.EXTRA_LARGE}
|
||||||
closeOnBackgroundClick={false}
|
closeOnBackgroundClick={false}
|
||||||
onModalClose={onModalClose}
|
onModalClose={onModalClose}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class BookshelfFooter extends Component {
|
|||||||
const {
|
const {
|
||||||
isSaving,
|
isSaving,
|
||||||
saveError
|
saveError
|
||||||
} = prevProps;
|
} = this.props;
|
||||||
|
|
||||||
if (prevProps.isSaving && !isSaving && !saveError) {
|
if (prevProps.isSaving && !isSaving && !saveError) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -143,7 +143,7 @@ class BookshelfFooter extends Component {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.label}>
|
<div className={styles.label}>
|
||||||
{selectedCount} Author(s) Selected
|
{translate('CountAuthorsSelected', { selectedCount })}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SpinnerButton
|
<SpinnerButton
|
||||||
|
|||||||
@@ -25,6 +25,10 @@
|
|||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.version {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: $breakpointMedium) {
|
@media only screen and (max-width: $breakpointMedium) {
|
||||||
.image {
|
.image {
|
||||||
height: 250px;
|
height: 250px;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ interface CssExports {
|
|||||||
'image': string;
|
'image': string;
|
||||||
'imageContainer': string;
|
'imageContainer': string;
|
||||||
'message': string;
|
'message': string;
|
||||||
|
'version': string;
|
||||||
}
|
}
|
||||||
export const cssExports: CssExports;
|
export const cssExports: CssExports;
|
||||||
export default cssExports;
|
export default cssExports;
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import styles from './ErrorBoundaryError.css';
|
|
||||||
|
|
||||||
function ErrorBoundaryError(props) {
|
|
||||||
const {
|
|
||||||
className,
|
|
||||||
messageClassName,
|
|
||||||
detailsClassName,
|
|
||||||
message,
|
|
||||||
error,
|
|
||||||
info
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={className}>
|
|
||||||
<div className={messageClassName}>
|
|
||||||
{message}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.imageContainer}>
|
|
||||||
<img
|
|
||||||
className={styles.image}
|
|
||||||
src={`${window.Readarr.urlBase}/Content/Images/error.png`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<details className={detailsClassName}>
|
|
||||||
{
|
|
||||||
error &&
|
|
||||||
<div>
|
|
||||||
{error.toString()}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div className={styles.info}>
|
|
||||||
{info.componentStack}
|
|
||||||
</div>
|
|
||||||
</details>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorBoundaryError.propTypes = {
|
|
||||||
className: PropTypes.string.isRequired,
|
|
||||||
messageClassName: PropTypes.string.isRequired,
|
|
||||||
detailsClassName: PropTypes.string.isRequired,
|
|
||||||
message: PropTypes.string.isRequired,
|
|
||||||
error: PropTypes.object.isRequired,
|
|
||||||
info: PropTypes.object.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
ErrorBoundaryError.defaultProps = {
|
|
||||||
className: styles.container,
|
|
||||||
messageClassName: styles.message,
|
|
||||||
detailsClassName: styles.details,
|
|
||||||
message: 'There was an error loading this content'
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ErrorBoundaryError;
|
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import StackTrace from 'stacktrace-js';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import styles from './ErrorBoundaryError.css';
|
||||||
|
|
||||||
|
interface ErrorBoundaryErrorProps {
|
||||||
|
className: string;
|
||||||
|
messageClassName: string;
|
||||||
|
detailsClassName: string;
|
||||||
|
message: string;
|
||||||
|
error: Error;
|
||||||
|
info: {
|
||||||
|
componentStack: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function ErrorBoundaryError(props: ErrorBoundaryErrorProps) {
|
||||||
|
const {
|
||||||
|
className = styles.container,
|
||||||
|
messageClassName = styles.message,
|
||||||
|
detailsClassName = styles.details,
|
||||||
|
message = translate('ErrorLoadingContent'),
|
||||||
|
error,
|
||||||
|
info,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const [detailedError, setDetailedError] = useState<
|
||||||
|
StackTrace.StackFrame[] | null
|
||||||
|
>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (error) {
|
||||||
|
StackTrace.fromError(error).then((de) => {
|
||||||
|
setDetailedError(de);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setDetailedError(null);
|
||||||
|
}
|
||||||
|
}, [error, setDetailedError]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={className}>
|
||||||
|
<div className={messageClassName}>{message}</div>
|
||||||
|
|
||||||
|
<div className={styles.imageContainer}>
|
||||||
|
<img
|
||||||
|
className={styles.image}
|
||||||
|
src={`${window.Readarr.urlBase}/Content/Images/error.png`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details className={detailsClassName}>
|
||||||
|
{error ? <div>{error.message}</div> : null}
|
||||||
|
|
||||||
|
{detailedError ? (
|
||||||
|
detailedError.map((d, index) => {
|
||||||
|
return (
|
||||||
|
<div key={index}>
|
||||||
|
{` at ${d.functionName} (${d.fileName}:${d.lineNumber}:${d.columnNumber})`}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<div>{info.componentStack}</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{
|
||||||
|
<div className={styles.version}>
|
||||||
|
Version: {window.Readarr.version}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ErrorBoundaryError;
|
||||||
@@ -206,9 +206,11 @@ class FilterBuilderRow extends Component {
|
|||||||
const selectedFilterBuilderProp = this.selectedFilterBuilderProp;
|
const selectedFilterBuilderProp = this.selectedFilterBuilderProp;
|
||||||
|
|
||||||
const keyOptions = filterBuilderProps.map((availablePropFilter) => {
|
const keyOptions = filterBuilderProps.map((availablePropFilter) => {
|
||||||
|
const { name, label } = availablePropFilter;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: availablePropFilter.name,
|
key: name,
|
||||||
value: availablePropFilter.label
|
value: typeof label === 'function' ? label() : label
|
||||||
};
|
};
|
||||||
}).sort((a, b) => a.value.localeCompare(b.value));
|
}).sort((a, b) => a.value.localeCompare(b.value));
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--inputHoverBackgroundColor);
|
background-color: var(--inputHoverBackgroundColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.isDisabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.optionCheck {
|
.optionCheck {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class NumberInput extends Component {
|
|||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
const { value } = this.props;
|
const { value } = this.props;
|
||||||
|
|
||||||
if (value !== prevProps.value && !this.state.isFocused) {
|
if (!isNaN(value) && value !== prevProps.value && !this.state.isFocused) {
|
||||||
this.setState({
|
this.setState({
|
||||||
value: value == null ? '' : value.toString()
|
value: value == null ? '' : value.toString()
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class SelectInput extends Component {
|
|||||||
value={key}
|
value={key}
|
||||||
{...otherOptionProps}
|
{...otherOptionProps}
|
||||||
>
|
>
|
||||||
{optionValue}
|
{typeof optionValue === 'function' ? optionValue() : optionValue}
|
||||||
</option>
|
</option>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
@@ -75,7 +75,7 @@ SelectInput.propTypes = {
|
|||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
disabledClassName: PropTypes.string,
|
disabledClassName: PropTypes.string,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.func]).isRequired,
|
||||||
values: PropTypes.arrayOf(PropTypes.object).isRequired,
|
values: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
isDisabled: PropTypes.bool,
|
isDisabled: PropTypes.bool,
|
||||||
hasError: PropTypes.bool,
|
hasError: PropTypes.bool,
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class Icon extends PureComponent {
|
|||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={containerClassName}
|
className={containerClassName}
|
||||||
title={title}
|
title={typeof title === 'function' ? title() : title}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
</span>
|
</span>
|
||||||
@@ -58,7 +58,7 @@ Icon.propTypes = {
|
|||||||
name: PropTypes.object.isRequired,
|
name: PropTypes.object.isRequired,
|
||||||
kind: PropTypes.string.isRequired,
|
kind: PropTypes.string.isRequired,
|
||||||
size: PropTypes.number.isRequired,
|
size: PropTypes.number.isRequired,
|
||||||
title: PropTypes.string,
|
title: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
||||||
isSpinning: PropTypes.bool.isRequired,
|
isSpinning: PropTypes.bool.isRequired,
|
||||||
fixedWidth: PropTypes.bool.isRequired
|
fixedWidth: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ class SpinnerErrorButton extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
|
kind,
|
||||||
isSpinning,
|
isSpinning,
|
||||||
error,
|
error,
|
||||||
children,
|
children,
|
||||||
@@ -112,7 +113,7 @@ class SpinnerErrorButton extends Component {
|
|||||||
const showIcon = wasSuccessful || hasWarning || hasError;
|
const showIcon = wasSuccessful || hasWarning || hasError;
|
||||||
|
|
||||||
let iconName = icons.CHECK;
|
let iconName = icons.CHECK;
|
||||||
let iconKind = kinds.SUCCESS;
|
let iconKind = kind === kinds.PRIMARY ? kinds.DEFAULT : kinds.SUCCESS;
|
||||||
|
|
||||||
if (hasWarning) {
|
if (hasWarning) {
|
||||||
iconName = icons.WARNING;
|
iconName = icons.WARNING;
|
||||||
@@ -126,6 +127,7 @@ class SpinnerErrorButton extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SpinnerButton
|
<SpinnerButton
|
||||||
|
kind={kind}
|
||||||
isSpinning={isSpinning}
|
isSpinning={isSpinning}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
>
|
>
|
||||||
@@ -154,6 +156,7 @@ class SpinnerErrorButton extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SpinnerErrorButton.propTypes = {
|
SpinnerErrorButton.propTypes = {
|
||||||
|
kind: PropTypes.oneOf(kinds.all),
|
||||||
isSpinning: PropTypes.bool.isRequired,
|
isSpinning: PropTypes.bool.isRequired,
|
||||||
error: PropTypes.object,
|
error: PropTypes.object,
|
||||||
children: PropTypes.node.isRequired
|
children: PropTypes.node.isRequired
|
||||||
|
|||||||
@@ -10,27 +10,55 @@ class InlineMarkdown extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
className,
|
className,
|
||||||
data
|
data,
|
||||||
|
blockClassName
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
// For now only replace links
|
// For now only replace links or code blocks (not both)
|
||||||
const markdownBlocks = [];
|
const markdownBlocks = [];
|
||||||
if (data) {
|
if (data) {
|
||||||
const regex = RegExp(/\[(.+?)\]\((.+?)\)/g);
|
const linkRegex = RegExp(/\[(.+?)\]\((.+?)\)/g);
|
||||||
|
|
||||||
let endIndex = 0;
|
let endIndex = 0;
|
||||||
let match = null;
|
let match = null;
|
||||||
while ((match = regex.exec(data)) !== null) {
|
|
||||||
|
while ((match = linkRegex.exec(data)) !== null) {
|
||||||
if (match.index > endIndex) {
|
if (match.index > endIndex) {
|
||||||
markdownBlocks.push(data.substr(endIndex, match.index - endIndex));
|
markdownBlocks.push(data.substr(endIndex, match.index - endIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
markdownBlocks.push(<Link key={match.index} to={match[2]}>{match[1]}</Link>);
|
markdownBlocks.push(<Link key={match.index} to={match[2]}>{match[1]}</Link>);
|
||||||
endIndex = match.index + match[0].length;
|
endIndex = match.index + match[0].length;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (endIndex !== data.length) {
|
if (endIndex !== data.length && markdownBlocks.length > 0) {
|
||||||
markdownBlocks.push(data.substr(endIndex, data.length - endIndex));
|
markdownBlocks.push(data.substr(endIndex, data.length - endIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const codeRegex = RegExp(/(?=`)`(?!`)[^`]*(?=`)`(?!`)/g);
|
||||||
|
|
||||||
|
endIndex = 0;
|
||||||
|
match = null;
|
||||||
|
let matchedCode = false;
|
||||||
|
|
||||||
|
while ((match = codeRegex.exec(data)) !== null) {
|
||||||
|
matchedCode = true;
|
||||||
|
|
||||||
|
if (match.index > endIndex) {
|
||||||
|
markdownBlocks.push(data.substr(endIndex, match.index - endIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
markdownBlocks.push(<code key={`code-${match.index}`} className={blockClassName ?? null}>{match[0].substring(1, match[0].length - 1)}</code>);
|
||||||
|
endIndex = match.index + match[0].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endIndex !== data.length && markdownBlocks.length > 0 && matchedCode) {
|
||||||
|
markdownBlocks.push(data.substr(endIndex, data.length - endIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (markdownBlocks.length === 0) {
|
||||||
|
markdownBlocks.push(data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return <span className={className}>{markdownBlocks}</span>;
|
return <span className={className}>{markdownBlocks}</span>;
|
||||||
@@ -39,7 +67,8 @@ class InlineMarkdown extends Component {
|
|||||||
|
|
||||||
InlineMarkdown.propTypes = {
|
InlineMarkdown.propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
data: PropTypes.string
|
data: PropTypes.string,
|
||||||
|
blockClassName: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
export default InlineMarkdown;
|
export default InlineMarkdown;
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class FilterMenuContent extends Component {
|
|||||||
selectedFilterKey={selectedFilterKey}
|
selectedFilterKey={selectedFilterKey}
|
||||||
onPress={onFilterSelect}
|
onPress={onFilterSelect}
|
||||||
>
|
>
|
||||||
{filter.label}
|
{typeof filter.label === 'function' ? filter.label() : filter.label}
|
||||||
</FilterMenuItem>
|
</FilterMenuItem>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ function ErrorPage(props) {
|
|||||||
const {
|
const {
|
||||||
version,
|
version,
|
||||||
isLocalStorageSupported,
|
isLocalStorageSupported,
|
||||||
hasTranslationsError,
|
translationsError,
|
||||||
authorError,
|
authorError,
|
||||||
customFiltersError,
|
customFiltersError,
|
||||||
tagsError,
|
tagsError,
|
||||||
@@ -21,8 +21,8 @@ function ErrorPage(props) {
|
|||||||
|
|
||||||
if (!isLocalStorageSupported) {
|
if (!isLocalStorageSupported) {
|
||||||
errorMessage = 'Local Storage is not supported or disabled. A plugin or private browsing may have disabled it.';
|
errorMessage = 'Local Storage is not supported or disabled. A plugin or private browsing may have disabled it.';
|
||||||
} else if (hasTranslationsError) {
|
} else if (translationsError) {
|
||||||
errorMessage = 'Failed to load translations from API';
|
errorMessage = getErrorMessage(translationsError, 'Failed to load translations from API');
|
||||||
} else if (authorError) {
|
} else if (authorError) {
|
||||||
errorMessage = getErrorMessage(authorError, 'Failed to load author from API');
|
errorMessage = getErrorMessage(authorError, 'Failed to load author from API');
|
||||||
} else if (customFiltersError) {
|
} else if (customFiltersError) {
|
||||||
@@ -55,7 +55,7 @@ function ErrorPage(props) {
|
|||||||
ErrorPage.propTypes = {
|
ErrorPage.propTypes = {
|
||||||
version: PropTypes.string.isRequired,
|
version: PropTypes.string.isRequired,
|
||||||
isLocalStorageSupported: PropTypes.bool.isRequired,
|
isLocalStorageSupported: PropTypes.bool.isRequired,
|
||||||
hasTranslationsError: PropTypes.bool.isRequired,
|
translationsError: PropTypes.object,
|
||||||
authorError: PropTypes.object,
|
authorError: PropTypes.object,
|
||||||
customFiltersError: PropTypes.object,
|
customFiltersError: PropTypes.object,
|
||||||
tagsError: PropTypes.object,
|
tagsError: PropTypes.object,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import React, { Component } from 'react';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions';
|
import { fetchTranslations, saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions';
|
||||||
import { fetchAuthor } from 'Store/Actions/authorActions';
|
import { fetchAuthor } from 'Store/Actions/authorActions';
|
||||||
import { fetchBooks } from 'Store/Actions/bookActions';
|
import { fetchBooks } from 'Store/Actions/bookActions';
|
||||||
import { fetchCustomFilters } from 'Store/Actions/customFilterActions';
|
import { fetchCustomFilters } from 'Store/Actions/customFilterActions';
|
||||||
@@ -52,6 +52,7 @@ const selectIsPopulated = createSelector(
|
|||||||
(state) => state.settings.metadataProfiles.isPopulated,
|
(state) => state.settings.metadataProfiles.isPopulated,
|
||||||
(state) => state.settings.importLists.isPopulated,
|
(state) => state.settings.importLists.isPopulated,
|
||||||
(state) => state.system.status.isPopulated,
|
(state) => state.system.status.isPopulated,
|
||||||
|
(state) => state.app.translations.isPopulated,
|
||||||
(
|
(
|
||||||
customFiltersIsPopulated,
|
customFiltersIsPopulated,
|
||||||
tagsIsPopulated,
|
tagsIsPopulated,
|
||||||
@@ -60,7 +61,8 @@ const selectIsPopulated = createSelector(
|
|||||||
qualityProfilesIsPopulated,
|
qualityProfilesIsPopulated,
|
||||||
metadataProfilesIsPopulated,
|
metadataProfilesIsPopulated,
|
||||||
importListsIsPopulated,
|
importListsIsPopulated,
|
||||||
systemStatusIsPopulated
|
systemStatusIsPopulated,
|
||||||
|
translationsIsPopulated
|
||||||
) => {
|
) => {
|
||||||
return (
|
return (
|
||||||
customFiltersIsPopulated &&
|
customFiltersIsPopulated &&
|
||||||
@@ -70,7 +72,8 @@ const selectIsPopulated = createSelector(
|
|||||||
qualityProfilesIsPopulated &&
|
qualityProfilesIsPopulated &&
|
||||||
metadataProfilesIsPopulated &&
|
metadataProfilesIsPopulated &&
|
||||||
importListsIsPopulated &&
|
importListsIsPopulated &&
|
||||||
systemStatusIsPopulated
|
systemStatusIsPopulated &&
|
||||||
|
translationsIsPopulated
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -84,6 +87,7 @@ const selectErrors = createSelector(
|
|||||||
(state) => state.settings.metadataProfiles.error,
|
(state) => state.settings.metadataProfiles.error,
|
||||||
(state) => state.settings.importLists.error,
|
(state) => state.settings.importLists.error,
|
||||||
(state) => state.system.status.error,
|
(state) => state.system.status.error,
|
||||||
|
(state) => state.app.translations.error,
|
||||||
(
|
(
|
||||||
customFiltersError,
|
customFiltersError,
|
||||||
tagsError,
|
tagsError,
|
||||||
@@ -92,7 +96,8 @@ const selectErrors = createSelector(
|
|||||||
qualityProfilesError,
|
qualityProfilesError,
|
||||||
metadataProfilesError,
|
metadataProfilesError,
|
||||||
importListsError,
|
importListsError,
|
||||||
systemStatusError
|
systemStatusError,
|
||||||
|
translationsError
|
||||||
) => {
|
) => {
|
||||||
const hasError = !!(
|
const hasError = !!(
|
||||||
customFiltersError ||
|
customFiltersError ||
|
||||||
@@ -102,7 +107,8 @@ const selectErrors = createSelector(
|
|||||||
qualityProfilesError ||
|
qualityProfilesError ||
|
||||||
metadataProfilesError ||
|
metadataProfilesError ||
|
||||||
importListsError ||
|
importListsError ||
|
||||||
systemStatusError
|
systemStatusError ||
|
||||||
|
translationsError
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -114,7 +120,8 @@ const selectErrors = createSelector(
|
|||||||
qualityProfilesError,
|
qualityProfilesError,
|
||||||
metadataProfilesError,
|
metadataProfilesError,
|
||||||
importListsError,
|
importListsError,
|
||||||
systemStatusError
|
systemStatusError,
|
||||||
|
translationsError
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -176,6 +183,9 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
dispatchFetchStatus() {
|
dispatchFetchStatus() {
|
||||||
dispatch(fetchStatus());
|
dispatch(fetchStatus());
|
||||||
},
|
},
|
||||||
|
dispatchFetchTranslations() {
|
||||||
|
dispatch(fetchTranslations());
|
||||||
|
},
|
||||||
onResize(dimensions) {
|
onResize(dimensions) {
|
||||||
dispatch(saveDimensions(dimensions));
|
dispatch(saveDimensions(dimensions));
|
||||||
},
|
},
|
||||||
@@ -210,6 +220,7 @@ class PageConnector extends Component {
|
|||||||
this.props.dispatchFetchImportLists();
|
this.props.dispatchFetchImportLists();
|
||||||
this.props.dispatchFetchUISettings();
|
this.props.dispatchFetchUISettings();
|
||||||
this.props.dispatchFetchStatus();
|
this.props.dispatchFetchStatus();
|
||||||
|
this.props.dispatchFetchTranslations();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +236,6 @@ class PageConnector extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
hasTranslationsError,
|
|
||||||
isPopulated,
|
isPopulated,
|
||||||
hasError,
|
hasError,
|
||||||
dispatchFetchAuthor,
|
dispatchFetchAuthor,
|
||||||
@@ -237,15 +247,15 @@ class PageConnector extends Component {
|
|||||||
dispatchFetchImportLists,
|
dispatchFetchImportLists,
|
||||||
dispatchFetchUISettings,
|
dispatchFetchUISettings,
|
||||||
dispatchFetchStatus,
|
dispatchFetchStatus,
|
||||||
|
dispatchFetchTranslations,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (hasTranslationsError || hasError || !this.state.isLocalStorageSupported) {
|
if (hasError || !this.state.isLocalStorageSupported) {
|
||||||
return (
|
return (
|
||||||
<ErrorPage
|
<ErrorPage
|
||||||
{...this.state}
|
{...this.state}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
hasTranslationsError={hasTranslationsError}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -266,7 +276,6 @@ class PageConnector extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PageConnector.propTypes = {
|
PageConnector.propTypes = {
|
||||||
hasTranslationsError: PropTypes.bool.isRequired,
|
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
hasError: PropTypes.bool.isRequired,
|
hasError: PropTypes.bool.isRequired,
|
||||||
isSidebarVisible: PropTypes.bool.isRequired,
|
isSidebarVisible: PropTypes.bool.isRequired,
|
||||||
@@ -280,6 +289,7 @@ PageConnector.propTypes = {
|
|||||||
dispatchFetchImportLists: PropTypes.func.isRequired,
|
dispatchFetchImportLists: PropTypes.func.isRequired,
|
||||||
dispatchFetchUISettings: PropTypes.func.isRequired,
|
dispatchFetchUISettings: PropTypes.func.isRequired,
|
||||||
dispatchFetchStatus: PropTypes.func.isRequired,
|
dispatchFetchStatus: PropTypes.func.isRequired,
|
||||||
|
dispatchFetchTranslations: PropTypes.func.isRequired,
|
||||||
onSidebarVisibleChange: PropTypes.func.isRequired
|
onSidebarVisibleChange: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -21,28 +21,28 @@ const SIDEBAR_WIDTH = parseInt(dimensions.sidebarWidth);
|
|||||||
const links = [
|
const links = [
|
||||||
{
|
{
|
||||||
iconName: icons.AUTHOR_CONTINUING,
|
iconName: icons.AUTHOR_CONTINUING,
|
||||||
title: 'Library',
|
title: () => translate('Library'),
|
||||||
to: '/',
|
to: '/',
|
||||||
alias: '/authors',
|
alias: '/authors',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
title: 'Authors',
|
title: () => translate('Authors'),
|
||||||
to: '/authors'
|
to: '/authors'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Books',
|
title: () => translate('Books'),
|
||||||
to: '/books'
|
to: '/books'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Add New',
|
title: () => translate('AddNew'),
|
||||||
to: '/add/search'
|
to: '/add/search'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Bookshelf',
|
title: () => translate('Bookshelf'),
|
||||||
to: '/shelf'
|
to: '/shelf'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Unmapped Files',
|
title: () => translate('UnmappedFiles'),
|
||||||
to: '/unmapped'
|
to: '/unmapped'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -50,26 +50,26 @@ const links = [
|
|||||||
|
|
||||||
{
|
{
|
||||||
iconName: icons.CALENDAR,
|
iconName: icons.CALENDAR,
|
||||||
title: 'Calendar',
|
title: () => translate('Calendar'),
|
||||||
to: '/calendar'
|
to: '/calendar'
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
iconName: icons.ACTIVITY,
|
iconName: icons.ACTIVITY,
|
||||||
title: 'Activity',
|
title: () => translate('Activity'),
|
||||||
to: '/activity/queue',
|
to: '/activity/queue',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
title: 'Queue',
|
title: () => translate('Queue'),
|
||||||
to: '/activity/queue',
|
to: '/activity/queue',
|
||||||
statusComponent: QueueStatusConnector
|
statusComponent: QueueStatusConnector
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'History',
|
title: () => translate('History'),
|
||||||
to: '/activity/history'
|
to: '/activity/history'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Blocklist',
|
title: () => translate('Blocklist'),
|
||||||
to: '/activity/blocklist'
|
to: '/activity/blocklist'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -77,15 +77,15 @@ const links = [
|
|||||||
|
|
||||||
{
|
{
|
||||||
iconName: icons.WARNING,
|
iconName: icons.WARNING,
|
||||||
title: 'Wanted',
|
title: () => translate('Wanted'),
|
||||||
to: '/wanted/missing',
|
to: '/wanted/missing',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
title: 'Missing',
|
title: () => translate('Missing'),
|
||||||
to: '/wanted/missing'
|
to: '/wanted/missing'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Cutoff Unmet',
|
title: () => translate('CutoffUnmet'),
|
||||||
to: '/wanted/cutoffunmet'
|
to: '/wanted/cutoffunmet'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -93,55 +93,55 @@ const links = [
|
|||||||
|
|
||||||
{
|
{
|
||||||
iconName: icons.SETTINGS,
|
iconName: icons.SETTINGS,
|
||||||
title: 'Settings',
|
title: () => translate('Settings'),
|
||||||
to: '/settings',
|
to: '/settings',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
title: 'Media Management',
|
title: () => translate('MediaManagement'),
|
||||||
to: '/settings/mediamanagement'
|
to: '/settings/mediamanagement'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Profiles',
|
title: () => translate('Profiles'),
|
||||||
to: '/settings/profiles'
|
to: '/settings/profiles'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Quality',
|
title: () => translate('Quality'),
|
||||||
to: '/settings/quality'
|
to: '/settings/quality'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: translate('CustomFormats'),
|
title: () => translate('CustomFormats'),
|
||||||
to: '/settings/customformats'
|
to: '/settings/customformats'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: translate('Indexers'),
|
title: () => translate('Indexers'),
|
||||||
to: '/settings/indexers'
|
to: '/settings/indexers'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Download Clients',
|
title: () => translate('DownloadClients'),
|
||||||
to: '/settings/downloadclients'
|
to: '/settings/downloadclients'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Import Lists',
|
title: () => translate('ImportLists'),
|
||||||
to: '/settings/importlists'
|
to: '/settings/importlists'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Connect',
|
title: () => translate('Connect'),
|
||||||
to: '/settings/connect'
|
to: '/settings/connect'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Metadata',
|
title: () => translate('Metadata'),
|
||||||
to: '/settings/metadata'
|
to: '/settings/metadata'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Tags',
|
title: () => translate('Tags'),
|
||||||
to: '/settings/tags'
|
to: '/settings/tags'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'General',
|
title: () => translate('General'),
|
||||||
to: '/settings/general'
|
to: '/settings/general'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'UI',
|
title: () => translate('Ui'),
|
||||||
to: '/settings/ui'
|
to: '/settings/ui'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -149,32 +149,32 @@ const links = [
|
|||||||
|
|
||||||
{
|
{
|
||||||
iconName: icons.SYSTEM,
|
iconName: icons.SYSTEM,
|
||||||
title: 'System',
|
title: () => translate('System'),
|
||||||
to: '/system/status',
|
to: '/system/status',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
title: 'Status',
|
title: () => translate('Status'),
|
||||||
to: '/system/status',
|
to: '/system/status',
|
||||||
statusComponent: HealthStatusConnector
|
statusComponent: HealthStatusConnector
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Tasks',
|
title: () => translate('Tasks'),
|
||||||
to: '/system/tasks'
|
to: '/system/tasks'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Backup',
|
title: () => translate('Backup'),
|
||||||
to: '/system/backup'
|
to: '/system/backup'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Updates',
|
title: () => translate('Updates'),
|
||||||
to: '/system/updates'
|
to: '/system/updates'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Events',
|
title: () => translate('Events'),
|
||||||
to: '/system/events'
|
to: '/system/events'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Log Files',
|
title: () => translate('LogFiles'),
|
||||||
to: '/system/logs/files'
|
to: '/system/logs/files'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class PageSidebarItem extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
<span className={isChildItem ? styles.noIcon : null}>
|
<span className={isChildItem ? styles.noIcon : null}>
|
||||||
{title}
|
{typeof title === 'function' ? title() : title}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -88,7 +88,7 @@ class PageSidebarItem extends Component {
|
|||||||
|
|
||||||
PageSidebarItem.propTypes = {
|
PageSidebarItem.propTypes = {
|
||||||
iconName: PropTypes.object,
|
iconName: PropTypes.object,
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
|
||||||
to: PropTypes.string.isRequired,
|
to: PropTypes.string.isRequired,
|
||||||
isActive: PropTypes.bool,
|
isActive: PropTypes.bool,
|
||||||
isActiveParent: PropTypes.bool,
|
isActiveParent: PropTypes.bool,
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
type PropertyFunction<T> = () => T;
|
||||||
|
|
||||||
interface Column {
|
interface Column {
|
||||||
name: string;
|
name: string;
|
||||||
label: string | React.ReactNode;
|
label: string | PropertyFunction<string> | React.ReactNode;
|
||||||
columnLabel?: string;
|
columnLabel?: string;
|
||||||
isSortable?: boolean;
|
isSortable?: boolean;
|
||||||
isVisible: boolean;
|
isVisible: boolean;
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ function Table(props) {
|
|||||||
{...getTableHeaderCellProps(otherProps)}
|
{...getTableHeaderCellProps(otherProps)}
|
||||||
{...column}
|
{...column}
|
||||||
>
|
>
|
||||||
{column.label}
|
{typeof column.label === 'function' ? column.label() : column.label}
|
||||||
</TableHeaderCell>
|
</TableHeaderCell>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ class TableHeaderCell extends Component {
|
|||||||
const {
|
const {
|
||||||
className,
|
className,
|
||||||
name,
|
name,
|
||||||
|
label,
|
||||||
columnLabel,
|
columnLabel,
|
||||||
isSortable,
|
isSortable,
|
||||||
isVisible,
|
isVisible,
|
||||||
@@ -53,7 +54,8 @@ class TableHeaderCell extends Component {
|
|||||||
{...otherProps}
|
{...otherProps}
|
||||||
component="th"
|
component="th"
|
||||||
className={className}
|
className={className}
|
||||||
title={columnLabel}
|
label={typeof label === 'function' ? label() : label}
|
||||||
|
title={typeof columnLabel === 'function' ? columnLabel() : columnLabel}
|
||||||
onPress={this.onPress}
|
onPress={this.onPress}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@@ -77,7 +79,8 @@ class TableHeaderCell extends Component {
|
|||||||
TableHeaderCell.propTypes = {
|
TableHeaderCell.propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
columnLabel: PropTypes.string,
|
label: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.node]),
|
||||||
|
columnLabel: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
||||||
isSortable: PropTypes.bool,
|
isSortable: PropTypes.bool,
|
||||||
isVisible: PropTypes.bool,
|
isVisible: PropTypes.bool,
|
||||||
isModifiable: PropTypes.bool,
|
isModifiable: PropTypes.bool,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ function TableOptionsColumn(props) {
|
|||||||
isDisabled={isModifiable === false}
|
isDisabled={isModifiable === false}
|
||||||
onChange={onVisibleChange}
|
onChange={onVisibleChange}
|
||||||
/>
|
/>
|
||||||
{label}
|
{typeof label === 'function' ? label() : label}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -56,7 +56,7 @@ function TableOptionsColumn(props) {
|
|||||||
|
|
||||||
TableOptionsColumn.propTypes = {
|
TableOptionsColumn.propTypes = {
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
label: PropTypes.string.isRequired,
|
label: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
|
||||||
isVisible: PropTypes.bool.isRequired,
|
isVisible: PropTypes.bool.isRequired,
|
||||||
isModifiable: PropTypes.bool.isRequired,
|
isModifiable: PropTypes.bool.isRequired,
|
||||||
index: PropTypes.number.isRequired,
|
index: PropTypes.number.isRequired,
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ class TableOptionsColumnDragSource extends Component {
|
|||||||
|
|
||||||
<TableOptionsColumn
|
<TableOptionsColumn
|
||||||
name={name}
|
name={name}
|
||||||
label={label}
|
label={typeof label === 'function' ? label() : label}
|
||||||
isVisible={isVisible}
|
isVisible={isVisible}
|
||||||
isModifiable={isModifiable}
|
isModifiable={isModifiable}
|
||||||
index={index}
|
index={index}
|
||||||
@@ -138,7 +138,7 @@ class TableOptionsColumnDragSource extends Component {
|
|||||||
|
|
||||||
TableOptionsColumnDragSource.propTypes = {
|
TableOptionsColumnDragSource.propTypes = {
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
label: PropTypes.string.isRequired,
|
label: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
|
||||||
isVisible: PropTypes.bool.isRequired,
|
isVisible: PropTypes.bool.isRequired,
|
||||||
isModifiable: PropTypes.bool.isRequired,
|
isModifiable: PropTypes.bool.isRequired,
|
||||||
index: PropTypes.number.isRequired,
|
index: PropTypes.number.isRequired,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { tooltipPositions } from 'Helpers/Props';
|
||||||
import Tooltip from './Tooltip';
|
import Tooltip from './Tooltip';
|
||||||
import styles from './Popover.css';
|
import styles from './Popover.css';
|
||||||
|
|
||||||
@@ -30,8 +31,13 @@ function Popover(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Popover.propTypes = {
|
Popover.propTypes = {
|
||||||
|
className: PropTypes.string,
|
||||||
|
bodyClassName: PropTypes.string,
|
||||||
|
anchor: PropTypes.node.isRequired,
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
body: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired
|
body: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired,
|
||||||
|
position: PropTypes.oneOf(tooltipPositions.all),
|
||||||
|
canFlip: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Popover;
|
export default Popover;
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
enum TooltipPosition {
|
||||||
|
Top = 'top',
|
||||||
|
Right = 'right',
|
||||||
|
Bottom = 'bottom',
|
||||||
|
Left = 'left',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TooltipPosition;
|
||||||
@@ -6,6 +6,7 @@ export const BOOKSHELF = 'bookshelf';
|
|||||||
export const KEY_VALUE_LIST = 'keyValueList';
|
export const KEY_VALUE_LIST = 'keyValueList';
|
||||||
export const MONITOR_BOOKS_SELECT = 'monitorBooksSelect';
|
export const MONITOR_BOOKS_SELECT = 'monitorBooksSelect';
|
||||||
export const MONITOR_NEW_ITEMS_SELECT = 'monitorNewItemsSelect';
|
export const MONITOR_NEW_ITEMS_SELECT = 'monitorNewItemsSelect';
|
||||||
|
export const FLOAT = 'float';
|
||||||
export const NUMBER = 'number';
|
export const NUMBER = 'number';
|
||||||
export const OAUTH = 'oauth';
|
export const OAUTH = 'oauth';
|
||||||
export const PASSWORD = 'password';
|
export const PASSWORD = 'password';
|
||||||
@@ -34,6 +35,7 @@ export const all = [
|
|||||||
KEY_VALUE_LIST,
|
KEY_VALUE_LIST,
|
||||||
MONITOR_BOOKS_SELECT,
|
MONITOR_BOOKS_SELECT,
|
||||||
MONITOR_NEW_ITEMS_SELECT,
|
MONITOR_NEW_ITEMS_SELECT,
|
||||||
|
FLOAT,
|
||||||
NUMBER,
|
NUMBER,
|
||||||
OAUTH,
|
OAUTH,
|
||||||
PASSWORD,
|
PASSWORD,
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ const columns = [
|
|||||||
name: 'customFormats',
|
name: 'customFormats',
|
||||||
label: React.createElement(Icon, {
|
label: React.createElement(Icon, {
|
||||||
name: icons.INTERACTIVE,
|
name: icons.INTERACTIVE,
|
||||||
title: translate('CustomFormat')
|
title: () => translate('CustomFormat')
|
||||||
}),
|
}),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
@@ -91,9 +91,9 @@ const filterExistingFilesOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const importModeOptions = [
|
const importModeOptions = [
|
||||||
{ key: 'chooseImportMode', value: translate('ChooseImportMethod'), disabled: true },
|
{ key: 'chooseImportMode', value: () => translate('ChooseImportMethod'), disabled: true },
|
||||||
{ key: 'move', value: translate('MoveFiles') },
|
{ key: 'move', value: () => translate('MoveFiles') },
|
||||||
{ key: 'copy', value: translate('HardlinkCopyFiles') }
|
{ key: 'copy', value: () => translate('HardlinkCopyFiles') }
|
||||||
];
|
];
|
||||||
|
|
||||||
const SELECT = 'select';
|
const SELECT = 'select';
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import Alert from 'Components/Alert';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
import Table from 'Components/Table/Table';
|
import Table from 'Components/Table/Table';
|
||||||
import TableBody from 'Components/Table/TableBody';
|
import TableBody from 'Components/Table/TableBody';
|
||||||
import { icons, sortDirections } from 'Helpers/Props';
|
import { icons, kinds, sortDirections } from 'Helpers/Props';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import InteractiveSearchRow from './InteractiveSearchRow';
|
import InteractiveSearchRow from './InteractiveSearchRow';
|
||||||
import styles from './InteractiveSearch.css';
|
import styles from './InteractiveSearch.css';
|
||||||
@@ -56,7 +57,7 @@ const columns = [
|
|||||||
name: 'customFormatScore',
|
name: 'customFormatScore',
|
||||||
label: React.createElement(Icon, {
|
label: React.createElement(Icon, {
|
||||||
name: icons.SCORE,
|
name: icons.SCORE,
|
||||||
title: translate('CustomFormatScore')
|
title: () => translate('CustomFormatScore')
|
||||||
}),
|
}),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
@@ -112,17 +113,17 @@ function InteractiveSearch(props) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
!isFetching && isPopulated && !totalReleasesCount ?
|
!isFetching && isPopulated && !totalReleasesCount ?
|
||||||
<div className={styles.blankpad}>
|
<Alert kind={kinds.INFO}>
|
||||||
No results found
|
{translate('NoResultsFound')}
|
||||||
</div> :
|
</Alert> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!!totalReleasesCount && isPopulated && !items.length ?
|
!!totalReleasesCount && isPopulated && !items.length ?
|
||||||
<div className={styles.blankpad}>
|
<Alert kind={kinds.WARNING}>
|
||||||
All results are hidden by the applied filter
|
{translate('AllResultsAreHiddenByTheAppliedFilter')}
|
||||||
</div> :
|
</Alert> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +158,7 @@ function InteractiveSearch(props) {
|
|||||||
{
|
{
|
||||||
totalReleasesCount !== items.length && !!items.length ?
|
totalReleasesCount !== items.length && !!items.length ?
|
||||||
<div className={styles.filteredMessage}>
|
<div className={styles.filteredMessage}>
|
||||||
Some results are hidden by the applied filter
|
{translate('SomeResultsAreHiddenByTheAppliedFilter')}
|
||||||
</div> :
|
</div> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
|||||||
import formatDateTime from 'Utilities/Date/formatDateTime';
|
import formatDateTime from 'Utilities/Date/formatDateTime';
|
||||||
import formatAge from 'Utilities/Number/formatAge';
|
import formatAge from 'Utilities/Number/formatAge';
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
import formatPreferredWordScore from 'Utilities/Number/formatPreferredWordScore';
|
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import Peers from './Peers';
|
import Peers from './Peers';
|
||||||
import styles from './InteractiveSearchRow.css';
|
import styles from './InteractiveSearchRow.css';
|
||||||
@@ -32,6 +32,18 @@ function getDownloadIcon(isGrabbing, isGrabbed, grabError) {
|
|||||||
return icons.DOWNLOAD;
|
return icons.DOWNLOAD;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDownloadKind(isGrabbed, grabError, downloadAllowed) {
|
||||||
|
if (isGrabbed) {
|
||||||
|
return kinds.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (grabError || !downloadAllowed) {
|
||||||
|
return kinds.DANGER;
|
||||||
|
}
|
||||||
|
|
||||||
|
return kinds.DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
function getDownloadTooltip(isGrabbing, isGrabbed, grabError) {
|
function getDownloadTooltip(isGrabbing, isGrabbed, grabError) {
|
||||||
if (isGrabbing) {
|
if (isGrabbing) {
|
||||||
return '';
|
return '';
|
||||||
@@ -172,7 +184,7 @@ class InteractiveSearchRow extends Component {
|
|||||||
<TableRowCell className={styles.customFormatScore}>
|
<TableRowCell className={styles.customFormatScore}>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
anchor={
|
anchor={
|
||||||
formatPreferredWordScore(customFormatScore, customFormats.length)
|
formatCustomFormatScore(customFormatScore, customFormats.length)
|
||||||
}
|
}
|
||||||
tooltip={<BookFormats formats={customFormats} />}
|
tooltip={<BookFormats formats={customFormats} />}
|
||||||
position={tooltipPositions.BOTTOM}
|
position={tooltipPositions.BOTTOM}
|
||||||
@@ -212,7 +224,7 @@ class InteractiveSearchRow extends Component {
|
|||||||
{
|
{
|
||||||
<SpinnerIconButton
|
<SpinnerIconButton
|
||||||
name={getDownloadIcon(isGrabbing, isGrabbed, grabError)}
|
name={getDownloadIcon(isGrabbing, isGrabbed, grabError)}
|
||||||
kind={grabError || !downloadAllowed ? kinds.DANGER : kinds.DEFAULT}
|
kind={getDownloadKind(isGrabbed, grabError, downloadAllowed)}
|
||||||
title={getDownloadTooltip(isGrabbing, isGrabbed, grabError)}
|
title={getDownloadTooltip(isGrabbing, isGrabbed, grabError)}
|
||||||
isSpinning={isGrabbing}
|
isSpinning={isGrabbing}
|
||||||
onPress={downloadAllowed ? this.onGrabPress : this.onConfirmGrabPress}
|
onPress={downloadAllowed ? this.onGrabPress : this.onConfirmGrabPress}
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ class CustomFormat extends Component {
|
|||||||
isOpen={this.state.isDeleteCustomFormatModalOpen}
|
isOpen={this.state.isDeleteCustomFormatModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('DeleteCustomFormat')}
|
title={translate('DeleteCustomFormat')}
|
||||||
message={translate('DeleteCustomFormatMessageText', [name])}
|
message={translate('DeleteCustomFormatMessageText', { name })}
|
||||||
confirmLabel={translate('Delete')}
|
confirmLabel={translate('Delete')}
|
||||||
isSpinning={isDeleting}
|
isSpinning={isDeleting}
|
||||||
onConfirm={this.onConfirmDeleteCustomFormat}
|
onConfirm={this.onConfirmDeleteCustomFormat}
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ class Specification extends Component {
|
|||||||
isOpen={this.state.isDeleteSpecificationModalOpen}
|
isOpen={this.state.isDeleteSpecificationModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('DeleteCondition')}
|
title={translate('DeleteCondition')}
|
||||||
message={translate('DeleteConditionMessageText', [name])}
|
message={translate('DeleteConditionMessageText', { name })}
|
||||||
confirmLabel={translate('Delete')}
|
confirmLabel={translate('Delete')}
|
||||||
onConfirm={this.onConfirmDeleteSpecification}
|
onConfirm={this.onConfirmDeleteSpecification}
|
||||||
onCancel={this.onDeleteSpecificationModalClose}
|
onCancel={this.onDeleteSpecificationModalClose}
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ class DownloadClient extends Component {
|
|||||||
isOpen={this.state.isDeleteDownloadClientModalOpen}
|
isOpen={this.state.isDeleteDownloadClientModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('DeleteDownloadClient')}
|
title={translate('DeleteDownloadClient')}
|
||||||
message={translate('DeleteDownloadClientMessageText', [name])}
|
message={translate('DeleteDownloadClientMessageText', { name })}
|
||||||
confirmLabel={translate('Delete')}
|
confirmLabel={translate('Delete')}
|
||||||
onConfirm={this.onConfirmDeleteDownloadClient}
|
onConfirm={this.onConfirmDeleteDownloadClient}
|
||||||
onCancel={this.onDeleteDownloadClientModalClose}
|
onCancel={this.onDeleteDownloadClientModalClose}
|
||||||
|
|||||||
+20
-4
@@ -27,9 +27,25 @@ interface ManageDownloadClientsEditModalContentProps {
|
|||||||
const NO_CHANGE = 'noChange';
|
const NO_CHANGE = 'noChange';
|
||||||
|
|
||||||
const enableOptions = [
|
const enableOptions = [
|
||||||
{ key: NO_CHANGE, value: translate('NoChange'), disabled: true },
|
{
|
||||||
{ key: 'enabled', value: translate('Enabled') },
|
key: NO_CHANGE,
|
||||||
{ key: 'disabled', value: translate('Disabled') },
|
get value() {
|
||||||
|
return translate('NoChange');
|
||||||
|
},
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'enabled',
|
||||||
|
get value() {
|
||||||
|
return translate('Enabled');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'disabled',
|
||||||
|
get value() {
|
||||||
|
return translate('Disabled');
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function ManageDownloadClientsEditModalContent(
|
function ManageDownloadClientsEditModalContent(
|
||||||
@@ -164,7 +180,7 @@ function ManageDownloadClientsEditModalContent(
|
|||||||
|
|
||||||
<ModalFooter className={styles.modalFooter}>
|
<ModalFooter className={styles.modalFooter}>
|
||||||
<div className={styles.selected}>
|
<div className={styles.selected}>
|
||||||
{translate('CountDownloadClientsSelected', [selectedCount])}
|
{translate('CountDownloadClientsSelected', { selectedCount })}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
+9
-9
@@ -36,37 +36,37 @@ type OnSelectedChangeCallback = React.ComponentProps<
|
|||||||
const COLUMNS = [
|
const COLUMNS = [
|
||||||
{
|
{
|
||||||
name: 'name',
|
name: 'name',
|
||||||
label: translate('Name'),
|
label: () => translate('Name'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'implementation',
|
name: 'implementation',
|
||||||
label: translate('Implementation'),
|
label: () => translate('Implementation'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'enable',
|
name: 'enable',
|
||||||
label: translate('Enabled'),
|
label: () => translate('Enabled'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'priority',
|
name: 'priority',
|
||||||
label: translate('Priority'),
|
label: () => translate('Priority'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'removeCompletedDownloads',
|
name: 'removeCompletedDownloads',
|
||||||
label: translate('RemoveCompleted'),
|
label: () => translate('RemoveCompleted'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'removeFailedDownloads',
|
name: 'removeFailedDownloads',
|
||||||
label: translate('RemoveFailed'),
|
label: () => translate('RemoveFailed'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
},
|
},
|
||||||
@@ -286,9 +286,9 @@ function ManageDownloadClientsModalContent(
|
|||||||
isOpen={isDeleteModalOpen}
|
isOpen={isDeleteModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('DeleteSelectedDownloadClients')}
|
title={translate('DeleteSelectedDownloadClients')}
|
||||||
message={translate('DeleteSelectedDownloadClientsMessageText', [
|
message={translate('DeleteSelectedDownloadClientsMessageText', {
|
||||||
selectedIds.length,
|
count: selectedIds.length,
|
||||||
])}
|
})}
|
||||||
confirmLabel={translate('Delete')}
|
confirmLabel={translate('Delete')}
|
||||||
onConfirm={onConfirmDelete}
|
onConfirm={onConfirmDelete}
|
||||||
onCancel={onDeleteModalClose}
|
onCancel={onDeleteModalClose}
|
||||||
|
|||||||
+18
-3
@@ -72,9 +72,24 @@ function TagsModalContent(props: TagsModalContentProps) {
|
|||||||
}, [tags, applyTags, onApplyTagsPress]);
|
}, [tags, applyTags, onApplyTagsPress]);
|
||||||
|
|
||||||
const applyTagsOptions = [
|
const applyTagsOptions = [
|
||||||
{ key: 'add', value: translate('Add') },
|
{
|
||||||
{ key: 'remove', value: translate('Remove') },
|
key: 'add',
|
||||||
{ key: 'replace', value: translate('Replace') },
|
get value() {
|
||||||
|
return translate('Add');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'remove',
|
||||||
|
get value() {
|
||||||
|
return translate('Remove');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'replace',
|
||||||
|
get value() {
|
||||||
|
return translate('Replace');
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import Alert from 'Components/Alert';
|
||||||
import FieldSet from 'Components/FieldSet';
|
import FieldSet from 'Components/FieldSet';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
|
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
|
||||||
import PageSectionContent from 'Components/Page/PageSectionContent';
|
import PageSectionContent from 'Components/Page/PageSectionContent';
|
||||||
import { icons } from 'Helpers/Props';
|
import { icons, kinds } from 'Helpers/Props';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import EditRemotePathMappingModalConnector from './EditRemotePathMappingModalConnector';
|
import EditRemotePathMappingModalConnector from './EditRemotePathMappingModalConnector';
|
||||||
import RemotePathMapping from './RemotePathMapping';
|
import RemotePathMapping from './RemotePathMapping';
|
||||||
@@ -50,6 +52,11 @@ class RemotePathMappings extends Component {
|
|||||||
errorMessage={translate('UnableToLoadRemotePathMappings')}
|
errorMessage={translate('UnableToLoadRemotePathMappings')}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
>
|
>
|
||||||
|
|
||||||
|
<Alert kind={kinds.INFO}>
|
||||||
|
<InlineMarkdown data={translate('RemotePathMappingsInfo', { app: 'Readarr', wikiLink: 'https://wiki.servarr.com/readarr/settings#remote-path-mappings' })} />
|
||||||
|
</Alert>
|
||||||
|
|
||||||
<div className={styles.remotePathMappingsHeader}>
|
<div className={styles.remotePathMappingsHeader}>
|
||||||
<div className={styles.host}>
|
<div className={styles.host}>
|
||||||
{translate('Host')}
|
{translate('Host')}
|
||||||
|
|||||||
@@ -103,7 +103,6 @@ class GeneralSettings extends Component {
|
|||||||
isResettingApiKey,
|
isResettingApiKey,
|
||||||
isWindows,
|
isWindows,
|
||||||
isWindowsService,
|
isWindowsService,
|
||||||
isDocker,
|
|
||||||
mode,
|
mode,
|
||||||
packageUpdateMechanism,
|
packageUpdateMechanism,
|
||||||
onInputChange,
|
onInputChange,
|
||||||
@@ -171,7 +170,6 @@ class GeneralSettings extends Component {
|
|||||||
settings={settings}
|
settings={settings}
|
||||||
isWindows={isWindows}
|
isWindows={isWindows}
|
||||||
packageUpdateMechanism={packageUpdateMechanism}
|
packageUpdateMechanism={packageUpdateMechanism}
|
||||||
isDocker={isDocker}
|
|
||||||
onInputChange={onInputChange}
|
onInputChange={onInputChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -214,7 +212,6 @@ GeneralSettings.propTypes = {
|
|||||||
hasSettings: PropTypes.bool.isRequired,
|
hasSettings: PropTypes.bool.isRequired,
|
||||||
isWindows: PropTypes.bool.isRequired,
|
isWindows: PropTypes.bool.isRequired,
|
||||||
isWindowsService: PropTypes.bool.isRequired,
|
isWindowsService: PropTypes.bool.isRequired,
|
||||||
isDocker: PropTypes.bool.isRequired,
|
|
||||||
mode: PropTypes.string.isRequired,
|
mode: PropTypes.string.isRequired,
|
||||||
packageUpdateMechanism: PropTypes.string.isRequired,
|
packageUpdateMechanism: PropTypes.string.isRequired,
|
||||||
onInputChange: PropTypes.func.isRequired,
|
onInputChange: PropTypes.func.isRequired,
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ function createMapStateToProps() {
|
|||||||
isResettingApiKey,
|
isResettingApiKey,
|
||||||
isWindows: systemStatus.isWindows,
|
isWindows: systemStatus.isWindows,
|
||||||
isWindowsService: systemStatus.isWindows && systemStatus.mode === 'service',
|
isWindowsService: systemStatus.isWindows && systemStatus.mode === 'service',
|
||||||
isDocker: systemStatus.isDocker,
|
|
||||||
mode: systemStatus.mode,
|
mode: systemStatus.mode,
|
||||||
packageUpdateMechanism: systemStatus.packageUpdateMechanism,
|
packageUpdateMechanism: systemStatus.packageUpdateMechanism,
|
||||||
...sectionSettings
|
...sectionSettings
|
||||||
|
|||||||
@@ -8,12 +8,17 @@ import { inputTypes, sizes } from 'Helpers/Props';
|
|||||||
import titleCase from 'Utilities/String/titleCase';
|
import titleCase from 'Utilities/String/titleCase';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
|
|
||||||
|
const branchValues = [
|
||||||
|
'master',
|
||||||
|
'develop',
|
||||||
|
'nightly'
|
||||||
|
];
|
||||||
|
|
||||||
function UpdateSettings(props) {
|
function UpdateSettings(props) {
|
||||||
const {
|
const {
|
||||||
advancedSettings,
|
advancedSettings,
|
||||||
settings,
|
settings,
|
||||||
isWindows,
|
isWindows,
|
||||||
isDocker,
|
|
||||||
packageUpdateMechanism,
|
packageUpdateMechanism,
|
||||||
onInputChange
|
onInputChange
|
||||||
} = props;
|
} = props;
|
||||||
@@ -44,32 +49,21 @@ function UpdateSettings(props) {
|
|||||||
|
|
||||||
updateOptions.push({ key: 'script', value: 'Script' });
|
updateOptions.push({ key: 'script', value: 'Script' });
|
||||||
|
|
||||||
if (isDocker) {
|
|
||||||
return (
|
|
||||||
<FieldSet legend={translate('Updates')}>
|
|
||||||
<div>
|
|
||||||
{translate('UpdatingIsDisabledInsideADockerContainerUpdateTheContainerImageInstead')}
|
|
||||||
</div>
|
|
||||||
</FieldSet>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FieldSet legend={translate('Updates')}>
|
<FieldSet legend={translate('Updates')}>
|
||||||
<FormGroup
|
<FormGroup
|
||||||
advancedSettings={advancedSettings}
|
advancedSettings={advancedSettings}
|
||||||
isAdvanced={true}
|
isAdvanced={true}
|
||||||
>
|
>
|
||||||
<FormLabel>
|
<FormLabel>{translate('Branch')}</FormLabel>
|
||||||
{translate('Branch')}
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.TEXT}
|
type={inputTypes.AUTO_COMPLETE}
|
||||||
name="branch"
|
name="branch"
|
||||||
helpText={usingExternalUpdateMechanism ? translate('UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism') : translate('UsingExternalUpdateMechanismBranchToUseToUpdateReadarr')}
|
helpText={usingExternalUpdateMechanism ? translate('UsingExternalUpdateMechanismBranchUsedByExternalUpdateMechanism') : translate('UsingExternalUpdateMechanismBranchToUseToUpdateReadarr')}
|
||||||
helpLink="https://wiki.servarr.com/readarr/faq#how-do-I-update-my-readarr"
|
helpLink="https://wiki.servarr.com/readarr/faq#how-do-I-update-my-readarr"
|
||||||
{...branch}
|
{...branch}
|
||||||
|
values={branchValues}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
readOnly={usingExternalUpdateMechanism}
|
readOnly={usingExternalUpdateMechanism}
|
||||||
/>
|
/>
|
||||||
@@ -83,14 +77,13 @@ function UpdateSettings(props) {
|
|||||||
isAdvanced={true}
|
isAdvanced={true}
|
||||||
size={sizes.MEDIUM}
|
size={sizes.MEDIUM}
|
||||||
>
|
>
|
||||||
<FormLabel>
|
<FormLabel>{translate('Automatic')}</FormLabel>
|
||||||
{translate('Automatic')}
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
name="updateAutomatically"
|
name="updateAutomatically"
|
||||||
helpText={translate('UpdateAutomaticallyHelpText')}
|
helpText={translate('UpdateAutomaticallyHelpText')}
|
||||||
|
helpTextWarning={updateMechanism.value === 'docker' ? translate('AutomaticUpdatesDisabledDocker', { appName: 'Readarr' }) : undefined}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...updateAutomatically}
|
{...updateAutomatically}
|
||||||
/>
|
/>
|
||||||
@@ -100,9 +93,7 @@ function UpdateSettings(props) {
|
|||||||
advancedSettings={advancedSettings}
|
advancedSettings={advancedSettings}
|
||||||
isAdvanced={true}
|
isAdvanced={true}
|
||||||
>
|
>
|
||||||
<FormLabel>
|
<FormLabel>{translate('Mechanism')}</FormLabel>
|
||||||
{translate('Mechanism')}
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.SELECT}
|
type={inputTypes.SELECT}
|
||||||
@@ -121,9 +112,7 @@ function UpdateSettings(props) {
|
|||||||
advancedSettings={advancedSettings}
|
advancedSettings={advancedSettings}
|
||||||
isAdvanced={true}
|
isAdvanced={true}
|
||||||
>
|
>
|
||||||
<FormLabel>
|
<FormLabel>{translate('ScriptPath')}</FormLabel>
|
||||||
{translate('ScriptPath')}
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.TEXT}
|
type={inputTypes.TEXT}
|
||||||
@@ -144,7 +133,6 @@ UpdateSettings.propTypes = {
|
|||||||
advancedSettings: PropTypes.bool.isRequired,
|
advancedSettings: PropTypes.bool.isRequired,
|
||||||
settings: PropTypes.object.isRequired,
|
settings: PropTypes.object.isRequired,
|
||||||
isWindows: PropTypes.bool.isRequired,
|
isWindows: PropTypes.bool.isRequired,
|
||||||
isDocker: PropTypes.bool.isRequired,
|
|
||||||
packageUpdateMechanism: PropTypes.string.isRequired,
|
packageUpdateMechanism: PropTypes.string.isRequired,
|
||||||
onInputChange: PropTypes.func.isRequired
|
onInputChange: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ class ImportList extends Component {
|
|||||||
isOpen={this.state.isDeleteImportListModalOpen}
|
isOpen={this.state.isDeleteImportListModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('DeleteImportList')}
|
title={translate('DeleteImportList')}
|
||||||
message={translate('DeleteImportListMessageText', [name])}
|
message={translate('DeleteImportListMessageText', { name })}
|
||||||
confirmLabel={translate('Delete')}
|
confirmLabel={translate('Delete')}
|
||||||
onConfirm={this.onConfirmDeleteImportList}
|
onConfirm={this.onConfirmDeleteImportList}
|
||||||
onCancel={this.onDeleteImportListModalClose}
|
onCancel={this.onDeleteImportListModalClose}
|
||||||
|
|||||||
+20
-4
@@ -27,9 +27,25 @@ interface ManageImportListsEditModalContentProps {
|
|||||||
const NO_CHANGE = 'noChange';
|
const NO_CHANGE = 'noChange';
|
||||||
|
|
||||||
const autoAddOptions = [
|
const autoAddOptions = [
|
||||||
{ key: NO_CHANGE, value: translate('NoChange'), disabled: true },
|
{
|
||||||
{ key: 'enabled', value: translate('Enabled') },
|
key: NO_CHANGE,
|
||||||
{ key: 'disabled', value: translate('Disabled') },
|
get value() {
|
||||||
|
return translate('NoChange');
|
||||||
|
},
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'enabled',
|
||||||
|
get value() {
|
||||||
|
return translate('Enabled');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'disabled',
|
||||||
|
get value() {
|
||||||
|
return translate('Disabled');
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function ManageImportListsEditModalContent(
|
function ManageImportListsEditModalContent(
|
||||||
@@ -168,7 +184,7 @@ function ManageImportListsEditModalContent(
|
|||||||
|
|
||||||
<ModalFooter className={styles.modalFooter}>
|
<ModalFooter className={styles.modalFooter}>
|
||||||
<div className={styles.selected}>
|
<div className={styles.selected}>
|
||||||
{translate('CountImportListsSelected', [selectedCount])}
|
{translate('CountImportListsSelected', { selectedCount })}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
+10
-10
@@ -36,43 +36,43 @@ type OnSelectedChangeCallback = React.ComponentProps<
|
|||||||
const COLUMNS = [
|
const COLUMNS = [
|
||||||
{
|
{
|
||||||
name: 'name',
|
name: 'name',
|
||||||
label: translate('Name'),
|
label: () => translate('Name'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'implementation',
|
name: 'implementation',
|
||||||
label: translate('Implementation'),
|
label: () => translate('Implementation'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'qualityProfileId',
|
name: 'qualityProfileId',
|
||||||
label: translate('QualityProfile'),
|
label: () => translate('QualityProfile'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'metadataProfileId',
|
name: 'metadataProfileId',
|
||||||
label: translate('MetadataProfile'),
|
label: () => translate('MetadataProfile'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'rootFolderPath',
|
name: 'rootFolderPath',
|
||||||
label: translate('RootFolder'),
|
label: () => translate('RootFolder'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'enableAutomaticAdd',
|
name: 'enableAutomaticAdd',
|
||||||
label: translate('AutoAdd'),
|
label: () => translate('AutoAdd'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'tags',
|
name: 'tags',
|
||||||
label: translate('Tags'),
|
label: () => translate('Tags'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
},
|
},
|
||||||
@@ -283,9 +283,9 @@ function ManageImportListsModalContent(
|
|||||||
isOpen={isDeleteModalOpen}
|
isOpen={isDeleteModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('DeleteSelectedImportLists')}
|
title={translate('DeleteSelectedImportLists')}
|
||||||
message={translate('DeleteSelectedImportListsMessageText', [
|
message={translate('DeleteSelectedImportListsMessageText', {
|
||||||
selectedIds.length,
|
count: selectedIds.length,
|
||||||
])}
|
})}
|
||||||
confirmLabel={translate('Delete')}
|
confirmLabel={translate('Delete')}
|
||||||
onConfirm={onConfirmDelete}
|
onConfirm={onConfirmDelete}
|
||||||
onCancel={onDeleteModalClose}
|
onCancel={onDeleteModalClose}
|
||||||
|
|||||||
@@ -70,9 +70,24 @@ function TagsModalContent(props: TagsModalContentProps) {
|
|||||||
}, [tags, applyTags, onApplyTagsPress]);
|
}, [tags, applyTags, onApplyTagsPress]);
|
||||||
|
|
||||||
const applyTagsOptions = [
|
const applyTagsOptions = [
|
||||||
{ key: 'add', value: translate('Add') },
|
{
|
||||||
{ key: 'remove', value: translate('Remove') },
|
key: 'add',
|
||||||
{ key: 'replace', value: translate('Replace') },
|
get value() {
|
||||||
|
return translate('Add');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'remove',
|
||||||
|
get value() {
|
||||||
|
return translate('Remove');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'replace',
|
||||||
|
get value() {
|
||||||
|
return translate('Replace');
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ class Indexer extends Component {
|
|||||||
isOpen={this.state.isDeleteIndexerModalOpen}
|
isOpen={this.state.isDeleteIndexerModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('DeleteIndexer')}
|
title={translate('DeleteIndexer')}
|
||||||
message={translate('DeleteIndexerMessageText', [name])}
|
message={translate('DeleteIndexerMessageText', { name })}
|
||||||
confirmLabel={translate('Delete')}
|
confirmLabel={translate('Delete')}
|
||||||
onConfirm={this.onConfirmDeleteIndexer}
|
onConfirm={this.onConfirmDeleteIndexer}
|
||||||
onCancel={this.onDeleteIndexerModalClose}
|
onCancel={this.onDeleteIndexerModalClose}
|
||||||
|
|||||||
+20
-4
@@ -27,9 +27,25 @@ interface ManageIndexersEditModalContentProps {
|
|||||||
const NO_CHANGE = 'noChange';
|
const NO_CHANGE = 'noChange';
|
||||||
|
|
||||||
const enableOptions = [
|
const enableOptions = [
|
||||||
{ key: NO_CHANGE, value: translate('NoChange'), disabled: true },
|
{
|
||||||
{ key: 'enabled', value: translate('Enabled') },
|
key: NO_CHANGE,
|
||||||
{ key: 'disabled', value: translate('Disabled') },
|
get value() {
|
||||||
|
return translate('NoChange');
|
||||||
|
},
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'enabled',
|
||||||
|
get value() {
|
||||||
|
return translate('Enabled');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'disabled',
|
||||||
|
get value() {
|
||||||
|
return translate('Disabled');
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function ManageIndexersEditModalContent(
|
function ManageIndexersEditModalContent(
|
||||||
@@ -162,7 +178,7 @@ function ManageIndexersEditModalContent(
|
|||||||
|
|
||||||
<ModalFooter className={styles.modalFooter}>
|
<ModalFooter className={styles.modalFooter}>
|
||||||
<div className={styles.selected}>
|
<div className={styles.selected}>
|
||||||
{translate('CountIndexersSelected', [selectedCount])}
|
{translate('CountIndexersSelected', { selectedCount })}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -36,43 +36,43 @@ type OnSelectedChangeCallback = React.ComponentProps<
|
|||||||
const COLUMNS = [
|
const COLUMNS = [
|
||||||
{
|
{
|
||||||
name: 'name',
|
name: 'name',
|
||||||
label: translate('Name'),
|
label: () => translate('Name'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'implementation',
|
name: 'implementation',
|
||||||
label: translate('Implementation'),
|
label: () => translate('Implementation'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'enableRss',
|
name: 'enableRss',
|
||||||
label: translate('EnableRSS'),
|
label: () => translate('EnableRSS'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'enableAutomaticSearch',
|
name: 'enableAutomaticSearch',
|
||||||
label: translate('EnableAutomaticSearch'),
|
label: () => translate('EnableAutomaticSearch'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'enableInteractiveSearch',
|
name: 'enableInteractiveSearch',
|
||||||
label: translate('EnableInteractiveSearch'),
|
label: () => translate('EnableInteractiveSearch'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'priority',
|
name: 'priority',
|
||||||
label: translate('Priority'),
|
label: () => translate('Priority'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'tags',
|
name: 'tags',
|
||||||
label: translate('Tags'),
|
label: () => translate('Tags'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
},
|
},
|
||||||
@@ -281,9 +281,9 @@ function ManageIndexersModalContent(props: ManageIndexersModalContentProps) {
|
|||||||
isOpen={isDeleteModalOpen}
|
isOpen={isDeleteModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('DeleteSelectedIndexers')}
|
title={translate('DeleteSelectedIndexers')}
|
||||||
message={translate('DeleteSelectedIndexersMessageText', [
|
message={translate('DeleteSelectedIndexersMessageText', {
|
||||||
selectedIds.length,
|
count: selectedIds.length,
|
||||||
])}
|
})}
|
||||||
confirmLabel={translate('Delete')}
|
confirmLabel={translate('Delete')}
|
||||||
onConfirm={onConfirmDelete}
|
onConfirm={onConfirmDelete}
|
||||||
onCancel={onDeleteModalClose}
|
onCancel={onDeleteModalClose}
|
||||||
|
|||||||
@@ -70,9 +70,24 @@ function TagsModalContent(props: TagsModalContentProps) {
|
|||||||
}, [tags, applyTags, onApplyTagsPress]);
|
}, [tags, applyTags, onApplyTagsPress]);
|
||||||
|
|
||||||
const applyTagsOptions = [
|
const applyTagsOptions = [
|
||||||
{ key: 'add', value: translate('Add') },
|
{
|
||||||
{ key: 'remove', value: translate('Remove') },
|
key: 'add',
|
||||||
{ key: 'replace', value: translate('Replace') },
|
get value() {
|
||||||
|
return translate('Add');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'remove',
|
||||||
|
get value() {
|
||||||
|
return translate('Remove');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'replace',
|
||||||
|
get value() {
|
||||||
|
return translate('Replace');
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class RootFolder extends Component {
|
|||||||
isOpen={this.state.isDeleteRootFolderModalOpen}
|
isOpen={this.state.isDeleteRootFolderModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('DeleteRootFolder')}
|
title={translate('DeleteRootFolder')}
|
||||||
message={translate('DeleteRootFolderMessageText', [name])}
|
message={translate('DeleteRootFolderMessageText', { name })}
|
||||||
confirmLabel={translate('Delete')}
|
confirmLabel={translate('Delete')}
|
||||||
onConfirm={this.onConfirmDeleteRootFolder}
|
onConfirm={this.onConfirmDeleteRootFolder}
|
||||||
onCancel={this.onDeleteRootFolderModalClose}
|
onCancel={this.onDeleteRootFolderModalClose}
|
||||||
|
|||||||
@@ -11,16 +11,51 @@ import { inputTypes, kinds } from 'Helpers/Props';
|
|||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
|
|
||||||
const writeAudioTagOptions = [
|
const writeAudioTagOptions = [
|
||||||
{ key: 'no', value: translate('WriteTagsNo') },
|
{
|
||||||
{ key: 'sync', value: translate('WriteTagsSync') },
|
key: 'no',
|
||||||
{ key: 'allFiles', value: translate('WriteTagsAll') },
|
get value() {
|
||||||
{ key: 'newFiles', value: translate('WriteTagsNew') }
|
return translate('WriteTagsNo');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'sync',
|
||||||
|
get value() {
|
||||||
|
return translate('WriteTagsSync');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'allFiles',
|
||||||
|
get value() {
|
||||||
|
return translate('WriteTagsAll');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'newFiles',
|
||||||
|
get value() {
|
||||||
|
return translate('WriteTagsNew');
|
||||||
|
}
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const writeBookTagOptions = [
|
const writeBookTagOptions = [
|
||||||
{ key: 'sync', value: translate('WriteTagsSync') },
|
{
|
||||||
{ key: 'allFiles', value: translate('WriteTagsAll') },
|
key: 'sync',
|
||||||
{ key: 'newFiles', value: translate('WriteTagsNew') }
|
get value() {
|
||||||
|
return translate('WriteTagsSync');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'allFiles',
|
||||||
|
get value() {
|
||||||
|
return translate('WriteTagsAll');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'newFiles',
|
||||||
|
get value() {
|
||||||
|
return translate('WriteTagsNew');
|
||||||
|
}
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
function MetadataProvider(props) {
|
function MetadataProvider(props) {
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ class Notification extends Component {
|
|||||||
onReleaseImport,
|
onReleaseImport,
|
||||||
onUpgrade,
|
onUpgrade,
|
||||||
onRename,
|
onRename,
|
||||||
|
onAuthorAdded,
|
||||||
onAuthorDelete,
|
onAuthorDelete,
|
||||||
onBookDelete,
|
onBookDelete,
|
||||||
onBookFileDelete,
|
onBookFileDelete,
|
||||||
@@ -73,6 +74,7 @@ class Notification extends Component {
|
|||||||
supportsOnReleaseImport,
|
supportsOnReleaseImport,
|
||||||
supportsOnUpgrade,
|
supportsOnUpgrade,
|
||||||
supportsOnRename,
|
supportsOnRename,
|
||||||
|
supportsOnAuthorAdded,
|
||||||
supportsOnAuthorDelete,
|
supportsOnAuthorDelete,
|
||||||
supportsOnBookDelete,
|
supportsOnBookDelete,
|
||||||
supportsOnBookFileDelete,
|
supportsOnBookFileDelete,
|
||||||
@@ -136,6 +138,14 @@ class Notification extends Component {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
supportsOnAuthorAdded && onAuthorAdded ?
|
||||||
|
<Label kind={kinds.SUCCESS}>
|
||||||
|
{translate('OnAuthorAdded')}
|
||||||
|
</Label> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
supportsOnAuthorDelete && onAuthorDelete ?
|
supportsOnAuthorDelete && onAuthorDelete ?
|
||||||
<Label kind={kinds.SUCCESS}>
|
<Label kind={kinds.SUCCESS}>
|
||||||
@@ -227,7 +237,7 @@ class Notification extends Component {
|
|||||||
isOpen={this.state.isDeleteNotificationModalOpen}
|
isOpen={this.state.isDeleteNotificationModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('DeleteNotification')}
|
title={translate('DeleteNotification')}
|
||||||
message={translate('DeleteNotificationMessageText', [name])}
|
message={translate('DeleteNotificationMessageText', { name })}
|
||||||
confirmLabel={translate('Delete')}
|
confirmLabel={translate('Delete')}
|
||||||
onConfirm={this.onConfirmDeleteNotification}
|
onConfirm={this.onConfirmDeleteNotification}
|
||||||
onCancel={this.onDeleteNotificationModalClose}
|
onCancel={this.onDeleteNotificationModalClose}
|
||||||
@@ -244,6 +254,7 @@ Notification.propTypes = {
|
|||||||
onReleaseImport: PropTypes.bool.isRequired,
|
onReleaseImport: PropTypes.bool.isRequired,
|
||||||
onUpgrade: PropTypes.bool.isRequired,
|
onUpgrade: PropTypes.bool.isRequired,
|
||||||
onRename: PropTypes.bool.isRequired,
|
onRename: PropTypes.bool.isRequired,
|
||||||
|
onAuthorAdded: PropTypes.bool.isRequired,
|
||||||
onAuthorDelete: PropTypes.bool.isRequired,
|
onAuthorDelete: PropTypes.bool.isRequired,
|
||||||
onBookDelete: PropTypes.bool.isRequired,
|
onBookDelete: PropTypes.bool.isRequired,
|
||||||
onBookFileDelete: PropTypes.bool.isRequired,
|
onBookFileDelete: PropTypes.bool.isRequired,
|
||||||
@@ -257,6 +268,7 @@ Notification.propTypes = {
|
|||||||
supportsOnReleaseImport: PropTypes.bool.isRequired,
|
supportsOnReleaseImport: PropTypes.bool.isRequired,
|
||||||
supportsOnUpgrade: PropTypes.bool.isRequired,
|
supportsOnUpgrade: PropTypes.bool.isRequired,
|
||||||
supportsOnRename: PropTypes.bool.isRequired,
|
supportsOnRename: PropTypes.bool.isRequired,
|
||||||
|
supportsOnAuthorAdded: PropTypes.bool.isRequired,
|
||||||
supportsOnAuthorDelete: PropTypes.bool.isRequired,
|
supportsOnAuthorDelete: PropTypes.bool.isRequired,
|
||||||
supportsOnBookDelete: PropTypes.bool.isRequired,
|
supportsOnBookDelete: PropTypes.bool.isRequired,
|
||||||
supportsOnBookFileDelete: PropTypes.bool.isRequired,
|
supportsOnBookFileDelete: PropTypes.bool.isRequired,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ function NotificationEventItems(props) {
|
|||||||
onReleaseImport,
|
onReleaseImport,
|
||||||
onUpgrade,
|
onUpgrade,
|
||||||
onRename,
|
onRename,
|
||||||
|
onAuthorAdded,
|
||||||
onAuthorDelete,
|
onAuthorDelete,
|
||||||
onBookDelete,
|
onBookDelete,
|
||||||
onBookFileDelete,
|
onBookFileDelete,
|
||||||
@@ -32,6 +33,7 @@ function NotificationEventItems(props) {
|
|||||||
supportsOnReleaseImport,
|
supportsOnReleaseImport,
|
||||||
supportsOnUpgrade,
|
supportsOnUpgrade,
|
||||||
supportsOnRename,
|
supportsOnRename,
|
||||||
|
supportsOnAuthorAdded,
|
||||||
supportsOnAuthorDelete,
|
supportsOnAuthorDelete,
|
||||||
supportsOnBookDelete,
|
supportsOnBookDelete,
|
||||||
supportsOnBookFileDelete,
|
supportsOnBookFileDelete,
|
||||||
@@ -123,6 +125,17 @@ function NotificationEventItems(props) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.CHECK}
|
||||||
|
name="onAuthorAdded"
|
||||||
|
helpText={translate('OnAuthorAddedHelpText')}
|
||||||
|
isDisabled={!supportsOnAuthorAdded.value}
|
||||||
|
{...onAuthorAdded}
|
||||||
|
onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ class MetadataProfile extends Component {
|
|||||||
isOpen={this.state.isDeleteMetadataProfileModalOpen}
|
isOpen={this.state.isDeleteMetadataProfileModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('DeleteMetadataProfile')}
|
title={translate('DeleteMetadataProfile')}
|
||||||
message={translate('DeleteMetadataProfileMessageText', [name])}
|
message={translate('DeleteMetadataProfileMessageText', { name })}
|
||||||
confirmLabel={translate('Delete')}
|
confirmLabel={translate('Delete')}
|
||||||
isSpinning={isDeleting}
|
isSpinning={isDeleting}
|
||||||
onConfirm={this.onConfirmDeleteMetadataProfile}
|
onConfirm={this.onConfirmDeleteMetadataProfile}
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ class QualityProfile extends Component {
|
|||||||
isOpen={this.state.isDeleteQualityProfileModalOpen}
|
isOpen={this.state.isDeleteQualityProfileModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('DeleteQualityProfile')}
|
title={translate('DeleteQualityProfile')}
|
||||||
message={translate('DeleteQualityProfileMessageText', [name])}
|
message={translate('DeleteQualityProfileMessageText', { name })}
|
||||||
confirmLabel={translate('Delete')}
|
confirmLabel={translate('Delete')}
|
||||||
isSpinning={isDeleting}
|
isSpinning={isDeleting}
|
||||||
onConfirm={this.onConfirmDeleteQualityProfile}
|
onConfirm={this.onConfirmDeleteQualityProfile}
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ function Settings() {
|
|||||||
className={styles.link}
|
className={styles.link}
|
||||||
to="/settings/ui"
|
to="/settings/ui"
|
||||||
>
|
>
|
||||||
{translate('UI')}
|
{translate('Ui')}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className={styles.summary}>
|
<div className={styles.summary}>
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ export default {
|
|||||||
selectedSchema.onReleaseImport = selectedSchema.supportsOnReleaseImport;
|
selectedSchema.onReleaseImport = selectedSchema.supportsOnReleaseImport;
|
||||||
selectedSchema.onUpgrade = selectedSchema.supportsOnUpgrade;
|
selectedSchema.onUpgrade = selectedSchema.supportsOnUpgrade;
|
||||||
selectedSchema.onRename = selectedSchema.supportsOnRename;
|
selectedSchema.onRename = selectedSchema.supportsOnRename;
|
||||||
|
selectedSchema.onAuthorAdded = selectedSchema.supportsOnAuthorAdded;
|
||||||
selectedSchema.onAuthorDelete = selectedSchema.supportsOnAuthorDelete;
|
selectedSchema.onAuthorDelete = selectedSchema.supportsOnAuthorDelete;
|
||||||
selectedSchema.onBookDelete = selectedSchema.supportsOnBookDelete;
|
selectedSchema.onBookDelete = selectedSchema.supportsOnBookDelete;
|
||||||
selectedSchema.onBookFileDelete = selectedSchema.supportsOnBookFileDelete;
|
selectedSchema.onBookFileDelete = selectedSchema.supportsOnBookFileDelete;
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHand
|
|||||||
import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler';
|
import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler';
|
||||||
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
|
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
|
||||||
import { createThunk } from 'Store/thunks';
|
import { createThunk } from 'Store/thunks';
|
||||||
|
import monitorNewItemsOptions from 'Utilities/Author/monitorNewItemsOptions';
|
||||||
|
import monitorOptions from 'Utilities/Author/monitorOptions';
|
||||||
|
|
||||||
//
|
//
|
||||||
// Variables
|
// Variables
|
||||||
@@ -51,6 +53,10 @@ export default {
|
|||||||
port: 8080,
|
port: 8080,
|
||||||
useSsl: false,
|
useSsl: false,
|
||||||
outputProfile: 'default',
|
outputProfile: 'default',
|
||||||
|
defaultQualityProfileId: 0,
|
||||||
|
defaultMetadataProfileId: 0,
|
||||||
|
defaultMonitorOption: monitorOptions[0].key,
|
||||||
|
defaultNewItemMonitorOption: monitorNewItemsOptions[0].key,
|
||||||
defaultTags: []
|
defaultTags: []
|
||||||
},
|
},
|
||||||
isSaving: false,
|
isSaving: false,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { createThunk, handleThunks } from 'Store/thunks';
|
|||||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||||
import getSectionState from 'Utilities/State/getSectionState';
|
import getSectionState from 'Utilities/State/getSectionState';
|
||||||
import updateSectionState from 'Utilities/State/updateSectionState';
|
import updateSectionState from 'Utilities/State/updateSectionState';
|
||||||
|
import { fetchTranslations as fetchAppTranslations } from 'Utilities/String/translate';
|
||||||
import createHandleActions from './Creators/createHandleActions';
|
import createHandleActions from './Creators/createHandleActions';
|
||||||
|
|
||||||
function getDimensions(width, height) {
|
function getDimensions(width, height) {
|
||||||
@@ -41,7 +42,12 @@ export const defaultState = {
|
|||||||
isReconnecting: false,
|
isReconnecting: false,
|
||||||
isDisconnected: false,
|
isDisconnected: false,
|
||||||
isRestarting: false,
|
isRestarting: false,
|
||||||
isSidebarVisible: !getDimensions(window.innerWidth, window.innerHeight).isSmallScreen
|
isSidebarVisible: !getDimensions(window.innerWidth, window.innerHeight).isSmallScreen,
|
||||||
|
translations: {
|
||||||
|
isFetching: true,
|
||||||
|
isPopulated: false,
|
||||||
|
error: null
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -53,6 +59,7 @@ export const SAVE_DIMENSIONS = 'app/saveDimensions';
|
|||||||
export const SET_VERSION = 'app/setVersion';
|
export const SET_VERSION = 'app/setVersion';
|
||||||
export const SET_APP_VALUE = 'app/setAppValue';
|
export const SET_APP_VALUE = 'app/setAppValue';
|
||||||
export const SET_IS_SIDEBAR_VISIBLE = 'app/setIsSidebarVisible';
|
export const SET_IS_SIDEBAR_VISIBLE = 'app/setIsSidebarVisible';
|
||||||
|
export const FETCH_TRANSLATIONS = 'app/fetchTranslations';
|
||||||
|
|
||||||
export const PING_SERVER = 'app/pingServer';
|
export const PING_SERVER = 'app/pingServer';
|
||||||
|
|
||||||
@@ -66,6 +73,7 @@ export const setAppValue = createAction(SET_APP_VALUE);
|
|||||||
export const showMessage = createAction(SHOW_MESSAGE);
|
export const showMessage = createAction(SHOW_MESSAGE);
|
||||||
export const hideMessage = createAction(HIDE_MESSAGE);
|
export const hideMessage = createAction(HIDE_MESSAGE);
|
||||||
export const pingServer = createThunk(PING_SERVER);
|
export const pingServer = createThunk(PING_SERVER);
|
||||||
|
export const fetchTranslations = createThunk(FETCH_TRANSLATIONS);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Helpers
|
// Helpers
|
||||||
@@ -127,6 +135,17 @@ function pingServerAfterTimeout(getState, dispatch) {
|
|||||||
export const actionHandlers = handleThunks({
|
export const actionHandlers = handleThunks({
|
||||||
[PING_SERVER]: function(getState, payload, dispatch) {
|
[PING_SERVER]: function(getState, payload, dispatch) {
|
||||||
pingServerAfterTimeout(getState, dispatch);
|
pingServerAfterTimeout(getState, dispatch);
|
||||||
|
},
|
||||||
|
[FETCH_TRANSLATIONS]: async function(getState, payload, dispatch) {
|
||||||
|
const isFetchingComplete = await fetchAppTranslations();
|
||||||
|
|
||||||
|
dispatch(setAppValue({
|
||||||
|
translations: {
|
||||||
|
isFetching: false,
|
||||||
|
isPopulated: isFetchingComplete,
|
||||||
|
error: isFetchingComplete ? null : 'Failed to load translations from API'
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ export const section = 'books';
|
|||||||
export const filters = [
|
export const filters = [
|
||||||
{
|
{
|
||||||
key: 'all',
|
key: 'all',
|
||||||
label: translate('All'),
|
label: () => translate('All'),
|
||||||
filters: []
|
filters: []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'monitored',
|
key: 'monitored',
|
||||||
label: translate('Monitored'),
|
label: () => translate('Monitored'),
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
key: 'monitored',
|
key: 'monitored',
|
||||||
@@ -40,7 +40,7 @@ export const filters = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'unmonitored',
|
key: 'unmonitored',
|
||||||
label: translate('Unmonitored'),
|
label: () => translate('Unmonitored'),
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
key: 'monitored',
|
key: 'monitored',
|
||||||
@@ -51,7 +51,7 @@ export const filters = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'missing',
|
key: 'missing',
|
||||||
label: translate('Missing'),
|
label: () => translate('Missing'),
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
key: 'monitored',
|
key: 'monitored',
|
||||||
@@ -67,7 +67,7 @@ export const filters = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'wanted',
|
key: 'wanted',
|
||||||
label: translate('Wanted'),
|
label: () => translate('Wanted'),
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
key: 'monitored',
|
key: 'monitored',
|
||||||
|
|||||||
@@ -60,32 +60,32 @@ export const defaultState = {
|
|||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
name: 'status',
|
name: 'status',
|
||||||
columnLabel: translate('Status'),
|
columnLabel: () => translate('Status'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
isModifiable: false
|
isModifiable: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'authorMetadata.sortName',
|
name: 'authorMetadata.sortName',
|
||||||
label: translate('Author'),
|
label: () => translate('Author'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'books.title',
|
name: 'books.title',
|
||||||
label: translate('BookTitle'),
|
label: () => translate('BookTitle'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'books.releaseDate',
|
name: 'books.releaseDate',
|
||||||
label: translate('ReleaseDate'),
|
label: () => translate('ReleaseDate'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: false
|
isVisible: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'quality',
|
name: 'quality',
|
||||||
label: translate('Quality'),
|
label: () => translate('Quality'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
@@ -97,64 +97,64 @@ export const defaultState = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'customFormatScore',
|
name: 'customFormatScore',
|
||||||
columnLabel: translate('CustomFormatScore'),
|
columnLabel: () => translate('CustomFormatScore'),
|
||||||
label: React.createElement(Icon, {
|
label: React.createElement(Icon, {
|
||||||
name: icons.SCORE,
|
name: icons.SCORE,
|
||||||
title: translate('CustomFormatScore')
|
title: () => translate('CustomFormatScore')
|
||||||
}),
|
}),
|
||||||
isVisible: false
|
isVisible: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'protocol',
|
name: 'protocol',
|
||||||
label: translate('Protocol'),
|
label: () => translate('Protocol'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: false
|
isVisible: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'indexer',
|
name: 'indexer',
|
||||||
label: translate('Indexer'),
|
label: () => translate('Indexer'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: false
|
isVisible: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'downloadClient',
|
name: 'downloadClient',
|
||||||
label: translate('DownloadClient'),
|
label: () => translate('DownloadClient'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: false
|
isVisible: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'title',
|
name: 'title',
|
||||||
label: translate('ReleaseTitle'),
|
label: () => translate('ReleaseTitle'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: false
|
isVisible: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'size',
|
name: 'size',
|
||||||
label: translate('Size'),
|
label: () => translate('Size'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: false
|
isVisible: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'outputPath',
|
name: 'outputPath',
|
||||||
label: translate('OutputPath'),
|
label: () => translate('OutputPath'),
|
||||||
isSortable: false,
|
isSortable: false,
|
||||||
isVisible: false
|
isVisible: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'estimatedCompletionTime',
|
name: 'estimatedCompletionTime',
|
||||||
label: translate('TimeLeft'),
|
label: () => translate('TimeLeft'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'progress',
|
name: 'progress',
|
||||||
label: translate('Progress'),
|
label: () => translate('Progress'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'actions',
|
name: 'actions',
|
||||||
columnLabel: translate('Actions'),
|
columnLabel: () => translate('Actions'),
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
isModifiable: false
|
isModifiable: false
|
||||||
}
|
}
|
||||||
@@ -371,13 +371,13 @@ export const actionHandlers = handleThunks({
|
|||||||
id,
|
id,
|
||||||
remove,
|
remove,
|
||||||
blocklist,
|
blocklist,
|
||||||
skipredownload
|
skipRedownload
|
||||||
} = payload;
|
} = payload;
|
||||||
|
|
||||||
dispatch(updateItem({ section: paged, id, isRemoving: true }));
|
dispatch(updateItem({ section: paged, id, isRemoving: true }));
|
||||||
|
|
||||||
const promise = createAjaxRequest({
|
const promise = createAjaxRequest({
|
||||||
url: `/queue/${id}?removeFromClient=${remove}&blocklist=${blocklist}&skipredownload=${skipredownload}`,
|
url: `/queue/${id}?removeFromClient=${remove}&blocklist=${blocklist}&skipRedownload=${skipRedownload}`,
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
}).request;
|
}).request;
|
||||||
|
|
||||||
@@ -395,7 +395,7 @@ export const actionHandlers = handleThunks({
|
|||||||
ids,
|
ids,
|
||||||
remove,
|
remove,
|
||||||
blocklist,
|
blocklist,
|
||||||
skipredownload
|
skipRedownload
|
||||||
} = payload;
|
} = payload;
|
||||||
|
|
||||||
dispatch(batchActions([
|
dispatch(batchActions([
|
||||||
@@ -411,7 +411,7 @@ export const actionHandlers = handleThunks({
|
|||||||
]));
|
]));
|
||||||
|
|
||||||
const promise = createAjaxRequest({
|
const promise = createAjaxRequest({
|
||||||
url: `/queue/bulk?removeFromClient=${remove}&blocklist=${blocklist}&skipredownload=${skipredownload}`,
|
url: `/queue/bulk?removeFromClient=${remove}&blocklist=${blocklist}&skipRedownload=${skipRedownload}`,
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
data: JSON.stringify({ ids })
|
data: JSON.stringify({ ids })
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ export const defaultState = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'customFormatScore',
|
name: 'customFormatScore',
|
||||||
label: translate('CustomFormatScore'),
|
label: () => translate('CustomFormatScore'),
|
||||||
type: filterBuilderTypes.NUMBER
|
type: filterBuilderTypes.NUMBER
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -82,34 +82,34 @@ export const defaultState = {
|
|||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
name: 'level',
|
name: 'level',
|
||||||
columnLabel: translate('Level'),
|
columnLabel: () => translate('Level'),
|
||||||
isSortable: false,
|
isSortable: false,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
isModifiable: false
|
isModifiable: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'time',
|
name: 'time',
|
||||||
label: translate('Time'),
|
label: () => translate('Time'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
isModifiable: false
|
isModifiable: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'logger',
|
name: 'logger',
|
||||||
label: translate('Component'),
|
label: () => translate('Component'),
|
||||||
isSortable: false,
|
isSortable: false,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
isModifiable: false
|
isModifiable: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'message',
|
name: 'message',
|
||||||
label: translate('Message'),
|
label: () => translate('Message'),
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
isModifiable: false
|
isModifiable: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'actions',
|
name: 'actions',
|
||||||
columnLabel: translate('Actions'),
|
columnLabel: () => translate('Actions'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
isModifiable: false
|
isModifiable: false
|
||||||
|
|||||||
@@ -36,10 +36,17 @@ function mergeColumns(path, initialState, persistedState, computedState) {
|
|||||||
const column = initialColumns.find((i) => i.name === persistedColumn.name);
|
const column = initialColumns.find((i) => i.name === persistedColumn.name);
|
||||||
|
|
||||||
if (column) {
|
if (column) {
|
||||||
columns.push({
|
const newColumn = {};
|
||||||
...column,
|
|
||||||
isVisible: persistedColumn.isVisible
|
// We can't use a spread operator or Object.assign to clone the column
|
||||||
});
|
// or any accessors are lost and can break translations.
|
||||||
|
for (const prop of Object.keys(column)) {
|
||||||
|
Object.defineProperty(newColumn, prop, Object.getOwnPropertyDescriptor(column, prop));
|
||||||
|
}
|
||||||
|
|
||||||
|
newColumn.isVisible = persistedColumn.isVisible;
|
||||||
|
|
||||||
|
columns.push(newColumn);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ class BackupRow extends Component {
|
|||||||
isOpen={isConfirmDeleteModalOpen}
|
isOpen={isConfirmDeleteModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('DeleteBackup')}
|
title={translate('DeleteBackup')}
|
||||||
message={translate('DeleteBackupMessageText', [name])}
|
message={translate('DeleteBackupMessageText', { name })}
|
||||||
confirmLabel={translate('Delete')}
|
confirmLabel={translate('Delete')}
|
||||||
onConfirm={this.onConfirmDeletePress}
|
onConfirm={this.onConfirmDeletePress}
|
||||||
onCancel={this.onConfirmDeleteModalClose}
|
onCancel={this.onConfirmDeleteModalClose}
|
||||||
|
|||||||
@@ -21,17 +21,17 @@ const columns = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'name',
|
name: 'name',
|
||||||
label: 'Name',
|
label: () => translate('Name'),
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'size',
|
name: 'size',
|
||||||
label: 'Size',
|
label: () => translate('Size'),
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'time',
|
name: 'time',
|
||||||
label: 'Time',
|
label: () => translate('Time'),
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ class RestoreBackupModalContent extends Component {
|
|||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
{
|
{
|
||||||
!!id && `Would you like to restore the backup '${name}'?`
|
!!id && translate('WouldYouLikeToRestoreBackup', { name })
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -19,12 +19,12 @@ import LogFilesTableRow from './LogFilesTableRow';
|
|||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
name: 'filename',
|
name: 'filename',
|
||||||
label: 'Filename',
|
label: () => translate('Filename'),
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'lastWriteTime',
|
name: 'lastWriteTime',
|
||||||
label: 'Last Write Time',
|
label: () => translate('LastWriteTime'),
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user