1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-24 17:25:22 -04:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Qstick
982e3c14d3 Fix Tests from Site Image Changes 2021-02-02 22:36:32 -05:00
571 changed files with 9996 additions and 13398 deletions

View File

@@ -110,13 +110,13 @@ dotnet_diagnostic.SA1643.severity = none
dotnet_diagnostic.SA1648.severity = none
dotnet_diagnostic.SA1649.severity = none
dotnet_diagnostic.SA1651.severity = none
dotnet_diagnostic.SX1101.severity = warning
dotnet_diagnostic.SX1309.severity = warning
# Microsoft Analyzers that fail and need to be sorted thru
dotnet_diagnostic.ASP0000.severity = suggestion
dotnet_diagnostic.CA1000.severity = suggestion
dotnet_diagnostic.CA1001.severity = suggestion
dotnet_diagnostic.CA1002.severity = suggestion
dotnet_diagnostic.CA1003.severity = suggestion
dotnet_diagnostic.CA1008.severity = suggestion
dotnet_diagnostic.CA1010.severity = suggestion
@@ -163,16 +163,10 @@ dotnet_diagnostic.CA1304.severity = suggestion
dotnet_diagnostic.CA1305.severity = suggestion
dotnet_diagnostic.CA1307.severity = suggestion
dotnet_diagnostic.CA1308.severity = suggestion
dotnet_diagnostic.CA1309.severity = suggestion
dotnet_diagnostic.CA1310.severity = suggestion
dotnet_diagnostic.CA1401.severity = suggestion
dotnet_diagnostic.CA1416.severity = suggestion
dotnet_diagnostic.CA1507.severity = suggestion
dotnet_diagnostic.CA1508.severity = suggestion
dotnet_diagnostic.CA1707.severity = suggestion
dotnet_diagnostic.CA1708.severity = suggestion
dotnet_diagnostic.CA1710.severity = suggestion
dotnet_diagnostic.CA1711.severity = suggestion
dotnet_diagnostic.CA1712.severity = suggestion
dotnet_diagnostic.CA1714.severity = suggestion
dotnet_diagnostic.CA1715.severity = suggestion
@@ -181,14 +175,12 @@ dotnet_diagnostic.CA1717.severity = suggestion
dotnet_diagnostic.CA1720.severity = suggestion
dotnet_diagnostic.CA1721.severity = suggestion
dotnet_diagnostic.CA1724.severity = suggestion
dotnet_diagnostic.CA1725.severity = suggestion
dotnet_diagnostic.CA1801.severity = suggestion
dotnet_diagnostic.CA1802.severity = suggestion
dotnet_diagnostic.CA1805.severity = suggestion
dotnet_diagnostic.CA1806.severity = suggestion
dotnet_diagnostic.CA1810.severity = suggestion
dotnet_diagnostic.CA1812.severity = suggestion
dotnet_diagnostic.CA1813.severity = suggestion
dotnet_diagnostic.CA1814.severity = suggestion
dotnet_diagnostic.CA1815.severity = suggestion
dotnet_diagnostic.CA1816.severity = suggestion
@@ -210,7 +202,6 @@ dotnet_diagnostic.CA2101.severity = suggestion
dotnet_diagnostic.CA2119.severity = suggestion
dotnet_diagnostic.CA2153.severity = suggestion
dotnet_diagnostic.CA2200.severity = suggestion
dotnet_diagnostic.CA2201.severity = suggestion
dotnet_diagnostic.CA2207.severity = suggestion
dotnet_diagnostic.CA2208.severity = suggestion
dotnet_diagnostic.CA2211.severity = suggestion
@@ -256,8 +247,6 @@ dotnet_diagnostic.CA5374.severity = suggestion
dotnet_diagnostic.CA5379.severity = suggestion
dotnet_diagnostic.CA5384.severity = suggestion
dotnet_diagnostic.CA5385.severity = suggestion
dotnet_diagnostic.CA5392.severity = suggestion
dotnet_diagnostic.CA5394.severity = suggestion
dotnet_diagnostic.CA5397.severity = suggestion

View File

@@ -1,37 +1,35 @@
---
name: Bug Report
about: Support Requests will be closed immediately, if you are not 100% certain this is a bug please go to our Reddit or Discord first. Exceptions do not mean you found a bug!
about: Support Requests will be closed immediately, if you are unsure go to our Reddit or Discord first. Exceptions do not mean you found a bug!
title: ''
labels: 'Type: Bug'
labels: bug
assignees: ''
---
<!-- Support Requests will be closed immediately, if you are unsure go to our Reddit or Discord first. Exceptions do not mean you found a bug! -->
**Describe the bug**
<!-- A clear and concise description of what the bug is. -->
A clear and concise description of what the bug is.
**To Reproduce**
<!-- Steps to reproduce the behavior:
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error -->
4. See error
**Expected behavior**
<!-- A clear and concise description of what you expected to happen.-->
A clear and concise description of what you expected to happen.
**Screenshots**
<!-- If applicable, add screenshots to help explain your problem.-->
If applicable, add screenshots to help explain your problem.
**Platform Information (please complete the following information):**
- OS: <!-- [e.g. Windows 10 2004 / Ubuntu 20.04] -->
- Docker: <!-- [Yes/No] -->
- Mono or .NET Version (System -> Status): <!--[e.g. Mono 5.8 or .Net Core 3.1.10 or .NET 5.0.1] -->
- Browser and Version (Only needed for UI issues): <!--[e.g. chrome 86.0.4240.198] -->
- Radarr Version: <!--[e.g. 3.0.1.4259, 3.0.2.4369]-->
- Radarr Branch: <!--[e.g. master, develop]-->
- OS: [e.g. Windows 10 2004 / Ubuntu 20.10]
- Docker: [Yes/No]
- Mono or.NET Core Version: [e.g. Mono 5.8 or .Net Core 3.1.10] (found under System -> Status)
- Browser and Version [e.g. chrome 86.0.4240.198] (Only needed for UI issues)
- Radarr Version [e.g. 3.0.0.2956]
- Radarr Branch [e.g. master]
**Trace Logs**
Turn on Trace logs under Settings -> General and wait for the bug to occur again.
**Upload the full log file here (or another site (e.g. pastebin) and link it). Issues will be closed, if they do not include this!**
<!-- Trace logs are named Radarr.trace.txt or Radarr.trace.#.txt and will contain "trace" in them-->
Turn on Trace logs under Settings -> General and wait for the bug to occur again. **Upload the full log file here (or another site (e.g. pastebin) and link it). Issues will be closed, if they do not include this!**

View File

@@ -1,7 +1,7 @@
blank_issues_enabled: false
contact_links:
- name: Support via Discord
url: https://radarr.video/discord
url: https://discord.gg/r5wJPt9
about: Chat with users and devs on support and setup related topics.
- name: Support via Reddit
url: https://reddit.com/r/radarr

View File

@@ -8,13 +8,13 @@ assignees: ''
---
**Is your feature request related to a problem? Please describe.**
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
<!-- A clear and concise description of what you want to happen. -->
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->
Add any other context or screenshots about the feature request here.

2
.github/config.yml vendored Normal file
View File

@@ -0,0 +1,2 @@
todo:
keyword: "TODO"

1
.github/reaction.yml vendored Normal file
View File

@@ -0,0 +1 @@

4
.github/stale.yml vendored
View File

@@ -5,10 +5,12 @@ daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- feature request
- 'Status: Confirmed'
- parser
- confirmed
- sonarr-pull
- lidarr-pull
- readarr-pull
- v3
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable

13
.github/support.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
# Configuration for support-requests - https://github.com/dessant/support-requests
# Label used to mark issues as support requests
supportLabel: support
# Comment to post on issues marked as support requests. Add a link
# to a support page, or set to `false` to disable
supportComment: >
We use the issue tracker exclusively for bug reports and feature requests.
However, this issue appears to be a support request. Please hop over onto our [Discord](https://discord.gg/r5wJPt9) or [Subreddit](https://reddit.com/r/radarr)
# Whether to close issues marked as support requests
close: true
# Whether to lock issues marked as support requests
lock: false

View File

@@ -1,21 +0,0 @@
name: 'Support requests'
on:
issues:
types: [labeled, unlabeled, reopened]
jobs:
support:
runs-on: ubuntu-latest
steps:
- uses: dessant/support-requests@v2
with:
github-token: ${{ github.token }}
support-label: 'Type: Support'
issue-comment: >
:wave: @{issue-author}, we use the issue tracker exclusively
for bug reports and feature requests. However, this issue appears
to be a support request. Please hop over onto our [Discord](https://radarr.video/discord)
or [Subreddit](https://reddit.com/r/radarr)
close-issue: true
lock-issue: false

View File

@@ -11,17 +11,17 @@ Setup guides, FAQ, the more information we have on the [wiki](https://wiki.serva
- Visual Studio 2019 or higher (https://www.visualstudio.com/vs/). The community version is free and works (https://www.visualstudio.com/downloads/).
- HTML/Javascript editor of choice (VS Code/Sublime Text/Webstorm/Atom/etc)
- [Git](https://git-scm.com/downloads)
- [NodeJS](https://nodejs.org/en/download/) (Node 12.X.X or higher)
- [NodeJS](https://nodejs.org/en/download/) (Node 10.X.X or higher)
- [Yarn](https://yarnpkg.com/)
- .NET Core 5.0.
- .NET Core 3.1.
### Getting started ###
1. Fork Radarr
2. Clone the repository into your development machine. [*info*](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository-from-github)
2. Clone the repository into your development machine. [*info*](https://help.github.com/articles/working-with-repositories)
3. Install the required Node Packages `yarn install`
4. Start gulp to monitor your dev environment for any changes that need post processing using `yarn start` command.
5. Build the project in Visual Studio, Setting startup project to `Radarr.Console` and framework to `net5.0`
5. Build the project in Visual Studio, Setting startup project to `Radarr.Console` and framework to `netcoreapp31`
6. Debug the project in Visual Studio
7. Open http://localhost:7878

View File

@@ -31,7 +31,7 @@ Radarr is a movie collection manager for Usenet and BitTorrent users. It can mon
## Support
Note: GitHub Issues are for Bugs and Feature Requests Only
[![Discord](https://img.shields.io/badge/discord-chat-7289DA.svg?maxAge=60)](https://radarr.video/discord)
[![Discord](https://img.shields.io/badge/discord-chat-7289DA.svg?maxAge=60)](https://discord.gg/r5wJPt9)
[![Reddit](https://img.shields.io/badge/reddit-discussion-FF4500.svg?maxAge=60)](https://www.reddit.com/r/Radarr)
[![GitHub - Bugs and Feature Requests Only](https://img.shields.io/badge/github-issues-red.svg?maxAge=60)](https://github.com/Radarr/Radarr/issues)
[![Wiki](https://img.shields.io/badge/servarr-wiki-181717.svg?maxAge=60)](https://wiki.servarr.com/Radarr)

View File

@@ -7,13 +7,13 @@ variables:
outputFolder: './_output'
artifactsFolder: './_artifacts'
testsFolder: './_tests'
majorVersion: '3.2.2'
majorVersion: '3.0.2'
minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '5.0.202'
dotnetVersion: '3.1.404'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
trigger:
@@ -23,12 +23,7 @@ trigger:
- master
pr:
branches:
include:
- develop
paths:
exclude:
- src/NzbDrone.Core/Localization/Core
- develop
stages:
- stage: Setup
@@ -64,21 +59,18 @@ stages:
Linux:
osName: 'Linux'
imageName: 'ubuntu-18.04'
enableAnalysis: 'true'
Mac:
osName: 'Mac'
imageName: 'macos-10.14'
enableAnalysis: 'false'
Windows:
osName: 'Windows'
imageName: 'windows-2019'
enableAnalysis: 'false'
pool:
vmImage: $(imageName)
variables:
# Disable stylecop here - linting errors get caught by the analyze task
EnableAnalyzers: $(enableAnalysis)
EnableAnalyzers: 'false'
steps:
- checkout: self
submodules: true
@@ -87,18 +79,7 @@ stages:
displayName: 'Install .net core'
inputs:
version: $(dotnetVersion)
- bash: |
BUNDLEDVERSIONS=${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}/Microsoft.NETCoreSdk.BundledVersions.props
echo $BUNDLEDVERSIONS
grep osx-x64 $BUNDLEDVERSIONS
if grep -q freebsd-x64 $BUNDLEDVERSIONS; then
echo "BSD already enabled"
else
echo "Enabling BSD support"
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64/' $BUNDLEDVERSIONS
fi
displayName: Enable FreeBSD Support
- bash: ./build.sh --backend --enable-bsd
- bash: ./build.sh --backend
displayName: Build Radarr Backend
- bash: |
find ${OUTPUTFOLDER} -type f ! -path "*/publish/*" -exec rm -rf {} \;
@@ -111,27 +92,23 @@ stages:
artifact: '$(osName)Backend'
displayName: Publish Backend
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net5.0/win-x64/publish'
- publish: '$(testsFolder)/netcoreapp3.1/win-x64/publish'
artifact: WindowsCoreTests
displayName: Publish Windows Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net472/linux-x64/publish'
- publish: '$(testsFolder)/net462/linux-x64/publish'
artifact: LinuxTests
displayName: Publish Linux Mono Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net5.0/linux-x64/publish'
- publish: '$(testsFolder)/netcoreapp3.1/linux-x64/publish'
artifact: LinuxCoreTests
displayName: Publish Linux Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net5.0/linux-musl-x64/publish'
- publish: '$(testsFolder)/netcoreapp3.1/linux-musl-x64/publish'
artifact: LinuxMuslCoreTests
displayName: Publish Linux Musl Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net5.0/freebsd-x64/publish'
artifact: FreebsdCoreTests
displayName: Publish FreeBSD Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net5.0/osx-x64/publish'
- publish: '$(testsFolder)/netcoreapp3.1/osx-x64/publish'
artifact: MacCoreTests
displayName: Publish MacOS Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
@@ -158,7 +135,7 @@ stages:
- task: NodeTool@0
displayName: Set Node.js version
inputs:
versionSpec: '12.x'
versionSpec: '10.x'
- checkout: self
submodules: true
fetchDepth: 1
@@ -207,12 +184,12 @@ stages:
- bash: ./build.sh --packages
displayName: Create Packages
- bash: |
setup/inno/ISCC.exe setup/radarr.iss //DFramework=net5.0 //DRuntime=win-x86
cp setup/output/Radarr.*windows.net5.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x86-installer.exe
setup/inno/ISCC.exe setup/radarr.iss //DFramework=netcoreapp3.1 //DRuntime=win-x86
cp setup/output/Radarr.*windows.netcoreapp3.1.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x86-installer.exe
displayName: Create .NET Core Windows installer
- bash: |
setup/inno/ISCC.exe setup/radarr.iss //DFramework=net5.0 //DRuntime=win-x64
cp setup/output/Radarr.*windows.net5.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x64-installer.exe
setup/inno/ISCC.exe setup/radarr.iss //DFramework=netcoreapp3.1 //DRuntime=win-x64
cp setup/output/Radarr.*windows.netcoreapp3.1.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x64-installer.exe
displayName: Create .NET Core Windows installer
- publish: $(Build.ArtifactStagingDirectory)
artifact: 'WindowsInstaller'
@@ -242,7 +219,7 @@ stages:
artifactName: WindowsFrontend
targetPath: _output
displayName: Fetch Frontend
- bash: ./build.sh --packages --enable-bsd
- bash: ./build.sh --packages
displayName: Create Packages
- bash: |
find . -name "Radarr" -exec chmod a+x {} \;
@@ -254,21 +231,21 @@ stages:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x64.zip'
archiveType: 'zip'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/win-x64/net5.0
rootFolderOrFile: $(artifactsFolder)/win-x64/netcoreapp3.1
- task: ArchiveFiles@2
displayName: Create Windows x86 Core zip
inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x86.zip'
archiveType: 'zip'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/win-x86/net5.0
rootFolderOrFile: $(artifactsFolder)/win-x86/netcoreapp3.1
- task: ArchiveFiles@2
displayName: Create MacOS Core app
inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-app-core-x64.zip'
archiveType: 'zip'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/macos-app/net5.0
rootFolderOrFile: $(artifactsFolder)/macos-app/netcoreapp3.1
- task: ArchiveFiles@2
displayName: Create MacOS Core tar
inputs:
@@ -276,7 +253,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/macos/net5.0
rootFolderOrFile: $(artifactsFolder)/macos/netcoreapp3.1
- task: ArchiveFiles@2
displayName: Create Linux Mono tar
inputs:
@@ -284,7 +261,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-x64/net472
rootFolderOrFile: $(artifactsFolder)/linux-x64/net462
- task: ArchiveFiles@2
displayName: Create Linux Core tar
inputs:
@@ -292,7 +269,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-x64/net5.0
rootFolderOrFile: $(artifactsFolder)/linux-x64/netcoreapp3.1
- task: ArchiveFiles@2
displayName: Create Linux Musl Core tar
inputs:
@@ -300,7 +277,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net5.0
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/netcoreapp3.1
- task: ArchiveFiles@2
displayName: Create ARM32 Linux Core tar
inputs:
@@ -308,7 +285,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-arm/net5.0
rootFolderOrFile: $(artifactsFolder)/linux-arm/netcoreapp3.1
- task: ArchiveFiles@2
displayName: Create ARM64 Linux Core tar
inputs:
@@ -316,7 +293,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-arm64/net5.0
rootFolderOrFile: $(artifactsFolder)/linux-arm64/netcoreapp3.1
- task: ArchiveFiles@2
displayName: Create ARM64 Linux Musl Core tar
inputs:
@@ -324,15 +301,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/net5.0
- task: ArchiveFiles@2
displayName: Create FreeBSD Core Core tar
inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).freebsd-core-x64.tar.gz'
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/freebsd-x64/net5.0
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/netcoreapp3.1
- publish: $(Build.ArtifactStagingDirectory)
artifact: 'Packages'
displayName: Publish Packages
@@ -386,34 +355,24 @@ stages:
displayName: Unit Native
dependsOn: Prepare
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
workspace:
clean: all
strategy:
matrix:
MacCore:
osName: 'Mac'
testName: 'MacCore'
poolName: 'Azure Pipelines'
imageName: 'macos-10.14'
WindowsCore:
osName: 'Windows'
testName: 'WindowsCore'
poolName: 'Azure Pipelines'
imageName: 'windows-2019'
LinuxCore:
osName: 'Linux'
testName: 'LinuxCore'
poolName: 'Azure Pipelines'
imageName: 'ubuntu-18.04'
FreebsdCore:
osName: 'Linux'
testName: 'FreebsdCore'
poolName: 'FreeBSD'
imageName:
pattern: 'Radarr.**.linux-core-x64.tar.gz'
pool:
name: $(poolName)
vmImage: $(imageName)
steps:
@@ -422,7 +381,6 @@ stages:
displayName: 'Install .net core'
inputs:
version: $(dotnetVersion)
condition: ne(variables['poolName'], 'FreeBSD')
- task: DownloadPipelineArtifact@2
displayName: Download Test Artifact
inputs:
@@ -435,7 +393,7 @@ stages:
sudo apt-get update
sudo apt-get install -y --allow-unauthenticated libmediainfo-dev libmediainfo0v5 mediainfo
displayName: Install mediainfo
condition: and(succeeded(), eq(variables['testName'], 'LinuxCore'))
condition: and(succeeded(), eq(variables['osName'], 'Linux'))
- powershell: Set-Service SCardSvr -StartupType Manual
displayName: Enable Windows Test Service
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
@@ -473,19 +431,19 @@ stages:
mono520:
testName: 'Mono 5.20'
artifactName: LinuxTests
containerImage: ghcr.io/servarr/testimages:mono-5.20
containerImage: servarr/testimages:mono-5.20
mono610:
testName: 'Mono 6.10'
artifactName: LinuxTests
containerImage: ghcr.io/servarr/testimages:mono-6.10
containerImage: servarr/testimages:mono-6.10
mono612:
testName: 'Mono 6.12'
artifactName: LinuxTests
containerImage: ghcr.io/servarr/testimages:mono-6.12
containerImage: servarr/testimages:mono-6.12
alpine:
testName: 'Musl Net Core'
artifactName: LinuxMuslCoreTests
containerImage: ghcr.io/servarr/testimages:alpine
containerImage: servarr/testimages:alpine
pool:
vmImage: 'ubuntu-18.04'
@@ -550,17 +508,17 @@ stages:
osName: 'Mac'
testName: 'MacCore'
imageName: 'macos-10.14'
pattern: 'Radarr.*.osx-core-x64.tar.gz'
pattern: 'Radarr.**.osx-core-x64.tar.gz'
WindowsCore:
osName: 'Windows'
testName: 'WindowsCore'
imageName: 'windows-2019'
pattern: 'Radarr.*.windows-core-x64.zip'
pattern: 'Radarr.**.windows-core-x64.zip'
LinuxCore:
osName: 'Linux'
testName: 'LinuxCore'
imageName: 'ubuntu-18.04'
pattern: 'Radarr.*.linux-core-x64.tar.gz'
pattern: 'Radarr.**.linux-core-x64.tar.gz'
pool:
vmImage: $(imageName)
@@ -613,52 +571,6 @@ stages:
failTaskOnFailedTests: true
displayName: Publish Test Results
- job: Integration_FreeBSD
displayName: Integration Native FreeBSD
dependsOn: Prepare
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
workspace:
clean: all
variables:
pattern: 'Radarr.*.freebsd-core-x64.tar.gz'
pool:
name: 'FreeBSD'
steps:
- checkout: none
- task: DownloadPipelineArtifact@2
displayName: Download Test Artifact
inputs:
buildType: 'current'
artifactName: 'FreebsdCoreTests'
targetPath: $(testsFolder)
- task: DownloadPipelineArtifact@2
displayName: Download Build Artifact
inputs:
buildType: 'current'
artifactName: Packages
itemPattern: '/$(pattern)'
targetPath: $(Build.ArtifactStagingDirectory)
- bash: |
mkdir -p ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin
tar xf ${BUILD_ARTIFACTSTAGINGDIRECTORY}/$(pattern) -C ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin
displayName: Extract Package
- bash: |
mkdir -p ./bin/
cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Radarr/. ./bin/
displayName: Move Package Contents
- 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: 'FreeBSD Integration Tests'
failTaskOnFailedTests: true
displayName: Publish Test Results
- job: Integration_Docker
displayName: Integration Docker
dependsOn: Prepare
@@ -668,23 +580,23 @@ stages:
mono520:
testName: 'Mono 5.20'
artifactName: LinuxTests
containerImage: ghcr.io/servarr/testimages:mono-5.20
pattern: 'Radarr.*.linux.tar.gz'
containerImage: servarr/testimages:mono-5.20
pattern: 'Radarr.**.linux.tar.gz'
mono610:
testName: 'Mono 6.10'
artifactName: LinuxTests
containerImage: ghcr.io/servarr/testimages:mono-6.10
pattern: 'Radarr.*.linux.tar.gz'
containerImage: servarr/testimages:mono-6.10
pattern: 'Radarr.**.linux.tar.gz'
mono612:
testName: 'Mono 6.12'
artifactName: LinuxTests
containerImage: ghcr.io/servarr/testimages:mono-6.12
pattern: 'Radarr.*.linux.tar.gz'
containerImage: servarr/testimages:mono-6.12
pattern: 'Radarr.**.linux.tar.gz'
alpine:
testName: 'Musl Net Core'
artifactName: LinuxMuslCoreTests
containerImage: ghcr.io/servarr/testimages:alpine
pattern: 'Radarr.*.linux-musl-core-x64.tar.gz'
containerImage: servarr/testimages:alpine
pattern: 'Radarr.**.linux-musl-core-x64.tar.gz'
pool:
vmImage: 'ubuntu-18.04'
@@ -743,17 +655,17 @@ stages:
Linux:
osName: 'Linux'
imageName: 'ubuntu-18.04'
pattern: 'Radarr.*.linux-core-x64.tar.gz'
pattern: 'Radarr.**.linux-core-x64.tar.gz'
failBuild: true
Mac:
osName: 'Mac'
imageName: 'macos-10.14'
pattern: 'Radarr.*.osx-core-x64.tar.gz'
pattern: 'Radarr.**.osx-core-x64.tar.gz'
failBuild: true
Windows:
osName: 'Windows'
imageName: 'windows-2019'
pattern: 'Radarr.*.windows-core-x64.zip'
pattern: 'Radarr.**.windows-core-x64.zip'
failBuild: true
pool:
@@ -845,7 +757,7 @@ stages:
- task: NodeTool@0
displayName: Set Node.js version
inputs:
versionSpec: '12.x'
versionSpec: '10.x'
- checkout: self
submodules: true
fetchDepth: 1
@@ -891,7 +803,6 @@ stages:
variables:
disable.coverage.autogenerate: 'true'
EnableAnalyzers: 'false'
pool:
vmImage: windows-2019
@@ -920,8 +831,8 @@ stages:
sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml
sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml
- bash: |
./build.sh --backend -f net5.0 -r win-x64
TEST_DIR=_tests/net5.0/win-x64/publish/ ./test.sh Windows Unit Coverage
./build.sh --backend -f netcoreapp3.1 -r win-x64
TEST_DIR=_tests/netcoreapp3.1/win-x64/publish/ ./test.sh Windows Unit Coverage
displayName: Coverage Unit Tests
- task: SonarCloudAnalyze@1
condition: eq(variables['System.PullRequest.IsFork'], 'False')

View File

@@ -1,4 +1,4 @@
#! /usr/bin/env bash
#! /bin/bash
set -e
outputFolder='_output'
@@ -25,18 +25,6 @@ UpdateVersionNumber()
fi
}
EnableBsdSupport()
{
#todo enable sdk with
#SDK_PATH=$(dotnet --list-sdks | grep -P '5\.\d\.\d+' | head -1 | sed 's/\(5\.[0-9]*\.[0-9]*\).*\[\(.*\)\]/\2\/\1/g')
# BUNDLED_VERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
if grep -qv freebsd-x64 src/Directory.Build.props; then
sed -i'' -e "s^<RuntimeIdentifiers>\(.*\)</RuntimeIdentifiers>^<RuntimeIdentifiers>\1;freebsd-x64</RuntimeIdentifiers>^g" src/Directory.Build.props
sed -i'' -e "s^<ExcludedRuntimeFrameworkPairs>\(.*\)</ExcludedRuntimeFrameworkPairs>^<ExcludedRuntimeFrameworkPairs>\1;freebsd-x64:net472</ExcludedRuntimeFrameworkPairs>^g" src/Directory.Build.props
fi
}
LintUI()
{
ProgressStart 'ESLint'
@@ -87,11 +75,11 @@ YarnInstall()
ProgressEnd 'yarn install'
}
RunWebpack()
RunGulp()
{
ProgressStart 'Running webpack'
yarn run build --env production
ProgressEnd 'Running webpack'
ProgressStart 'Running gulp'
yarn run build --production
ProgressEnd 'Running gulp'
}
PackageFiles()
@@ -130,7 +118,7 @@ PackageLinux()
echo "Adding Radarr.Mono to UpdatePackage"
cp $folder/Radarr.Mono.* $folder/Radarr.Update
if [ "$framework" = "net5.0" ]; then
if [ "$framework" = "netcoreapp3.1" ]; then
cp $folder/Mono.Posix.NETStandard.* $folder/Radarr.Update
cp $folder/libMonoPosixHelper.* $folder/Radarr.Update
fi
@@ -148,7 +136,7 @@ PackageMacOS()
PackageFiles "$folder" "$framework" "osx-x64"
if [ "$framework" = "net472" ]; then
if [ "$framework" = "net462" ]; then
echo "Adding Startup script"
cp macOS/Radarr $folder
fi
@@ -162,7 +150,7 @@ PackageMacOS()
echo "Adding Radarr.Mono to UpdatePackage"
cp $folder/Radarr.Mono.* $folder/Radarr.Update
if [ "$framework" = "net5.0" ]; then
if [ "$framework" = "netcoreapp3.1" ]; then
cp $folder/Mono.Posix.NETStandard.* $folder/Radarr.Update
cp $folder/libMonoPosixHelper.* $folder/Radarr.Update
fi
@@ -202,7 +190,6 @@ PackageWindows()
local folder=$artifactsFolder/$runtime/$framework/Radarr
PackageFiles "$folder" "$framework" "$runtime"
cp -r $outputFolder/$framework-windows/$runtime/publish/* $folder
echo "Removing Radarr.Mono"
rm -f $folder/Radarr.Mono.*
@@ -224,7 +211,7 @@ Package()
IFS='-' read -ra SPLIT <<< "$runtime"
case "${SPLIT[0]}" in
linux|freebsd*)
linux)
PackageLinux "$framework" "$runtime"
;;
win)
@@ -269,7 +256,6 @@ if [ $# -eq 0 ]; then
FRONTEND=YES
PACKAGES=YES
LINT=YES
ENABLE_BSD=NO
fi
while [[ $# -gt 0 ]]
@@ -281,10 +267,6 @@ case $key in
BACKEND=YES
shift # past argument
;;
--enable-bsd)
ENABLE_BSD=YES
shift # past argument
;;
-r|--runtime)
RID="$2"
shift # past argument
@@ -325,23 +307,15 @@ set -- "${POSITIONAL[@]}" # restore positional parameters
if [ "$BACKEND" = "YES" ];
then
UpdateVersionNumber
if [ "$ENABLE_BSD" = "YES" ];
then
EnableBsdSupport
fi
Build
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
then
PackageTests "net5.0" "win-x64"
PackageTests "net5.0" "win-x86"
PackageTests "net5.0" "linux-x64"
PackageTests "net5.0" "linux-musl-x64"
PackageTests "net5.0" "osx-x64"
PackageTests "net472" "linux-x64"
if [ "$ENABLE_BSD" = "YES" ];
then
PackageTests "net5.0" "freebsd-x64"
fi
PackageTests "netcoreapp3.1" "win-x64"
PackageTests "netcoreapp3.1" "win-x86"
PackageTests "netcoreapp3.1" "linux-x64"
PackageTests "netcoreapp3.1" "linux-musl-x64"
PackageTests "netcoreapp3.1" "osx-x64"
PackageTests "net462" "linux-x64"
else
PackageTests "$FRAMEWORK" "$RID"
fi
@@ -350,7 +324,7 @@ fi
if [ "$FRONTEND" = "YES" ];
then
YarnInstall
RunWebpack
RunGulp
fi
if [ "$LINT" = "YES" ];
@@ -369,19 +343,15 @@ then
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
then
Package "net5.0" "win-x64"
Package "net5.0" "win-x86"
Package "net5.0" "linux-x64"
Package "net5.0" "linux-musl-x64"
Package "net5.0" "linux-arm64"
Package "net5.0" "linux-musl-arm64"
Package "net5.0" "linux-arm"
Package "net5.0" "osx-x64"
Package "net472" "linux-x64"
if [ "$ENABLE_BSD" = "YES" ];
then
Package "net5.0" "freebsd-x64"
fi
Package "netcoreapp3.1" "win-x64"
Package "netcoreapp3.1" "win-x86"
Package "netcoreapp3.1" "linux-x64"
Package "netcoreapp3.1" "linux-musl-x64"
Package "netcoreapp3.1" "linux-arm64"
Package "netcoreapp3.1" "linux-musl-arm64"
Package "netcoreapp3.1" "linux-arm"
Package "netcoreapp3.1" "osx-x64"
Package "net462" "linux-x64"
else
Package "$FRAMEWORK" "$RID"
fi

View File

@@ -6,10 +6,8 @@ const dirs = fs
.map((dirent) => dirent.name)
.join('|');
const frontendFolder = __dirname;
module.exports = {
parser: '@babel/eslint-parser',
parser: 'babel-eslint',
env: {
browser: true,
@@ -27,9 +25,6 @@ module.exports = {
parserOptions: {
ecmaVersion: 6,
sourceType: 'module',
babelOptions: {
configFile: `${frontendFolder}/babel.config.js`
},
ecmaFeatures: {
modules: true,
impliedStrict: true
@@ -276,7 +271,7 @@ module.exports = {
// ImportSort
'simple-import-sort/imports': 'error',
'simple-import-sort/sort': 'error',
'import/newline-after-import': 'error',
// React
@@ -314,7 +309,7 @@ module.exports = {
{
files: ['*.js'],
rules: {
'simple-import-sort/imports': [
'simple-import-sort/sort': [
'error',
{
groups: [

View File

@@ -1,269 +0,0 @@
const path = require('path');
const webpack = require('webpack');
const CopyPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const LiveReloadPlugin = require('webpack-livereload-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = (env) => {
const uiFolder = 'UI';
const frontendFolder = path.join(__dirname, '..');
const srcFolder = path.join(frontendFolder, 'src');
const isProduction = !!env.production;
const isProfiling = isProduction && !!env.profile;
const inlineWebWorkers = 'no-fallback';
const distFolder = path.resolve(frontendFolder, '..', '_output', uiFolder);
console.log('Source Folder:', srcFolder);
console.log('Output Folder:', distFolder);
console.log('isProduction:', isProduction);
console.log('isProfiling:', isProfiling);
const config = {
mode: isProduction ? 'production' : 'development',
devtool: 'source-map',
stats: {
children: false
},
watchOptions: {
ignored: /node_modules/
},
entry: {
index: 'index.js'
},
resolve: {
modules: [
srcFolder,
path.join(srcFolder, 'Shims'),
'node_modules'
],
alias: {
jquery: 'jquery/src/jquery'
},
fallback: {
buffer: false,
http: false,
https: false,
url: false,
util: false,
net: false
}
},
output: {
path: distFolder,
publicPath: '/',
filename: '[name].js',
sourceMapFilename: '[file].map'
},
optimization: {
moduleIds: 'deterministic',
chunkIds: 'named',
splitChunks: {
chunks: 'initial',
name: 'vendors'
}
},
performance: {
hints: false
},
plugins: [
new webpack.DefinePlugin({
__DEV__: !isProduction,
'process.env.NODE_ENV': isProduction ? JSON.stringify('production') : JSON.stringify('development')
}),
new MiniCssExtractPlugin({
filename: 'Content/styles.css'
}),
new HtmlWebpackPlugin({
template: 'frontend/src/index.html',
filename: 'index.html',
publicPath: '/'
}),
new CopyPlugin({
patterns: [
// HTML
{
from: 'frontend/src/*.html',
to: path.join(distFolder, '[name][ext]'),
globOptions: {
ignore: ['**/index.html']
}
},
// Fonts
{
from: 'frontend/src/Content/Fonts/*.*',
to: path.join(distFolder, 'Content/Fonts', '[name][ext]')
},
// Icon Images
{
from: 'frontend/src/Content/Images/Icons/*.*',
to: path.join(distFolder, 'Content/Images/Icons', '[name][ext]')
},
// Images
{
from: 'frontend/src/Content/Images/*.*',
to: path.join(distFolder, 'Content/Images', '[name][ext]')
},
// Robots
{
from: 'frontend/src/Content/robots.txt',
to: path.join(distFolder, 'Content', '[name][ext]')
}
]
}),
new LiveReloadPlugin()
],
resolveLoader: {
modules: [
'node_modules',
'frontend/build/webpack/'
]
},
module: {
rules: [
{
test: /\.worker\.js$/,
use: {
loader: 'worker-loader',
options: {
filename: '[name].js',
inline: inlineWebWorkers
}
}
},
{
test: /\.js?$/,
exclude: /(node_modules|JsLibraries)/,
use: [
{
loader: 'babel-loader',
options: {
configFile: `${frontendFolder}/babel.config.js`,
envName: isProduction ? 'production' : 'development',
presets: [
[
'@babel/preset-env',
{
modules: false,
loose: true,
debug: false,
useBuiltIns: 'entry',
corejs: 3
}
]
]
}
}
]
},
// CSS Modules
{
test: /\.css$/,
exclude: /(node_modules|globals.css)/,
use: [
{ loader: MiniCssExtractPlugin.loader },
{
loader: 'css-loader',
options: {
importLoaders: 1,
modules: {
localIdentName: '[name]/[local]/[hash:base64:5]'
}
}
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
config: 'frontend/postcss.config.js'
}
}
}
]
},
// Global styles
{
test: /\.css$/,
include: /(node_modules|globals.css)/,
use: [
'style-loader',
{
loader: 'css-loader'
}
]
},
// Fonts
{
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10240,
mimetype: 'application/font-woff',
emitFile: false,
name: 'Content/Fonts/[name].[ext]'
}
}
]
},
{
test: /\.(ttf|eot|eot?#iefix|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
use: [
{
loader: 'file-loader',
options: {
emitFile: false,
name: 'Content/Fonts/[name].[ext]'
}
}
]
}
]
}
};
if (isProfiling) {
config.resolve.alias['react-dom$'] = 'react-dom/profiling';
config.resolve.alias['scheduler/tracing'] = 'scheduler/tracing-profiling';
config.optimization.minimizer = [
new TerserPlugin({
cache: true,
parallel: true,
sourceMap: true, // Must be set to true if using source-maps in production
terserOptions: {
mangle: false,
keep_classnames: true,
keep_fnames: true
}
})
];
}
return config;
};

18
frontend/gulp/build.js Normal file
View File

@@ -0,0 +1,18 @@
const gulp = require('gulp');
require('./clean');
require('./copy');
require('./webpack');
gulp.task('build',
gulp.series('clean',
gulp.parallel(
'webpack',
'copyHtml',
'copyFonts',
'copyImages',
'copyRobots'
)
)
);

8
frontend/gulp/clean.js Normal file
View File

@@ -0,0 +1,8 @@
const gulp = require('gulp');
const del = require('del');
const paths = require('./helpers/paths');
gulp.task('clean', () => {
return del([paths.dest.root]);
});

42
frontend/gulp/copy.js Normal file
View File

@@ -0,0 +1,42 @@
const path = require('path');
const gulp = require('gulp');
const print = require('gulp-print').default;
const cache = require('gulp-cached');
const livereload = require('gulp-livereload');
const paths = require('./helpers/paths.js');
gulp.task('copyHtml', () => {
return gulp.src(paths.src.html, { base: paths.src.root })
.pipe(cache('copyHtml'))
.pipe(print())
.pipe(gulp.dest(paths.dest.root))
.pipe(livereload());
});
gulp.task('copyFonts', () => {
return gulp.src(
path.join(paths.src.fonts, '**', '*.*'), { base: paths.src.root }
)
.pipe(cache('copyFonts'))
.pipe(print())
.pipe(gulp.dest(paths.dest.root))
.pipe(livereload());
});
gulp.task('copyImages', () => {
return gulp.src(
path.join(paths.src.images, '**', '*.*'), { base: paths.src.root }
)
.pipe(cache('copyImages'))
.pipe(print())
.pipe(gulp.dest(paths.dest.root))
.pipe(livereload());
});
gulp.task('copyRobots', () => {
return gulp.src(paths.src.robots, { base: paths.src.root })
.pipe(cache('copyRobots'))
.pipe(print())
.pipe(gulp.dest(paths.dest.root))
.pipe(livereload());
});

View File

@@ -0,0 +1,5 @@
require('./build.js');
require('./clean.js');
require('./copy.js');
require('./watch.js');
require('./webpack.js');

View File

@@ -0,0 +1,6 @@
const colors = require('ansi-colors');
module.exports = function errorHandler(error) {
console.log(colors.red(`Error (${error.plugin}): ${error.message}`));
this.emit('end');
};

View File

@@ -0,0 +1,24 @@
const root = './frontend/src';
const paths = {
src: {
root,
html: `${root}/*.html`,
scripts: `${root}/**/*.js`,
content: `${root}/Content/`,
fonts: `${root}/Content/Fonts/`,
images: `${root}/Content/Images/`,
robots: `${root}/Content/robots.txt`,
exclude: {
libs: `!${root}/JsLibraries/**`
}
},
dest: {
root: './_output/UI/',
content: './_output/UI/Content/',
fonts: './_output/UI/Content/Fonts/',
images: './_output/UI/Content/Images/'
}
};
module.exports = paths;

19
frontend/gulp/watch.js Normal file
View File

@@ -0,0 +1,19 @@
const gulp = require('gulp');
const livereload = require('gulp-livereload');
const gulpWatch = require('gulp-watch');
const paths = require('./helpers/paths.js');
require('./copy.js');
require('./webpack.js');
function watch() {
livereload.listen({ start: true });
gulp.task('webpackWatch')();
gulpWatch(paths.src.html, gulp.series('copyHtml'));
gulpWatch(`${paths.src.fonts}**/*.*`, gulp.series('copyFonts'));
gulpWatch(`${paths.src.images}**/*.*`, gulp.series('copyImages'));
gulpWatch(paths.src.robots, gulp.series('copyRobots'));
}
gulp.task('watch', gulp.series('build', watch));

271
frontend/gulp/webpack.js Normal file
View File

@@ -0,0 +1,271 @@
const gulp = require('gulp');
const webpackStream = require('webpack-stream');
const livereload = require('gulp-livereload');
const path = require('path');
const webpack = require('webpack');
const errorHandler = require('./helpers/errorHandler');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackPluginHtmlTags = require('html-webpack-plugin/lib/html-tags');
const TerserPlugin = require('terser-webpack-plugin');
const uiFolder = 'UI';
const frontendFolder = path.join(__dirname, '..');
const srcFolder = path.join(frontendFolder, 'src');
const isProduction = process.argv.indexOf('--production') > -1;
const isProfiling = isProduction && process.argv.indexOf('--profile') > -1;
const inlineWebWorkers = 'no-fallback';
const distFolder = path.resolve(frontendFolder, '..', '_output', uiFolder);
console.log('Source Folder:', srcFolder);
console.log('Output Folder:', distFolder);
console.log('isProduction:', isProduction);
console.log('isProfiling:', isProfiling);
const cssVarsFiles = [
'../src/Styles/Variables/colors',
'../src/Styles/Variables/dimensions',
'../src/Styles/Variables/fonts',
'../src/Styles/Variables/animations',
'../src/Styles/Variables/zIndexes'
].map(require.resolve);
// Override the way HtmlWebpackPlugin injects the scripts
// TODO: Find a better way to get these paths without
HtmlWebpackPlugin.prototype.injectAssetsIntoHtml = function(html, assets, assetTags) {
const head = assetTags.headTags.map((v) => {
const href = v.attributes.href
.replace('\\', '/')
.replace('%5C', '/');
v.attributes = { rel: 'stylesheet', type: 'text/css', href: `/${href}` };
return HtmlWebpackPluginHtmlTags.htmlTagObjectToString(v, this.options.xhtml);
});
const body = assetTags.bodyTags.map((v) => {
v.attributes = { src: `/${v.attributes.src}` };
return HtmlWebpackPluginHtmlTags.htmlTagObjectToString(v, this.options.xhtml);
});
return html
.replace('<!-- webpack bundles head -->', head.join('\r\n '))
.replace('<!-- webpack bundles body -->', body.join('\r\n '));
};
const plugins = [
new OptimizeCssAssetsPlugin({}),
new webpack.DefinePlugin({
__DEV__: !isProduction,
'process.env.NODE_ENV': isProduction ? JSON.stringify('production') : JSON.stringify('development')
}),
new MiniCssExtractPlugin({
filename: path.join('Content', 'styles.css')
}),
new HtmlWebpackPlugin({
template: 'frontend/src/index.html',
filename: 'index.html'
})
];
const config = {
mode: isProduction ? 'production' : 'development',
devtool: '#source-map',
stats: {
children: false
},
watchOptions: {
ignored: /node_modules/
},
entry: {
index: 'index.js'
},
resolve: {
modules: [
srcFolder,
path.join(srcFolder, 'Shims'),
'node_modules'
],
alias: {
jquery: 'jquery/src/jquery'
}
},
output: {
path: distFolder,
filename: '[name].js',
sourceMapFilename: '[file].map'
},
optimization: {
chunkIds: 'named',
splitChunks: {
chunks: 'initial'
}
},
performance: {
hints: false
},
plugins,
resolveLoader: {
modules: [
'node_modules',
'frontend/gulp/webpack/'
]
},
module: {
rules: [
{
test: /\.worker\.js$/,
use: {
loader: 'worker-loader',
options: {
filename: '[name].js',
inline: inlineWebWorkers
}
}
},
{
test: /\.js?$/,
exclude: /(node_modules|JsLibraries)/,
use: [
{
loader: 'babel-loader',
options: {
configFile: `${frontendFolder}/babel.config.js`,
envName: isProduction ? 'production' : 'development',
presets: [
[
'@babel/preset-env',
{
modules: false,
loose: true,
debug: false,
useBuiltIns: 'entry',
corejs: 3
}
]
]
}
}
]
},
// CSS Modules
{
test: /\.css$/,
exclude: /(node_modules|globals.css)/,
use: [
{ loader: MiniCssExtractPlugin.loader },
{
loader: 'css-loader',
options: {
importLoaders: 1,
modules: {
localIdentName: '[name]/[local]/[hash:base64:5]'
}
}
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
config: {
ctx: {
cssVarsFiles
},
path: 'frontend/postcss.config.js'
}
}
}
]
},
// Global styles
{
test: /\.css$/,
include: /(node_modules|globals.css)/,
use: [
'style-loader',
{
loader: 'css-loader'
}
]
},
// Fonts
{
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10240,
mimetype: 'application/font-woff',
emitFile: false,
name: 'Content/Fonts/[name].[ext]'
}
}
]
},
{
test: /\.(ttf|eot|eot?#iefix|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
use: [
{
loader: 'file-loader',
options: {
emitFile: false,
name: 'Content/Fonts/[name].[ext]'
}
}
]
}
]
}
};
if (isProfiling) {
config.resolve.alias['react-dom$'] = 'react-dom/profiling';
config.resolve.alias['scheduler/tracing'] = 'scheduler/tracing-profiling';
config.optimization.minimizer = [
new TerserPlugin({
cache: true,
parallel: true,
sourceMap: true, // Must be set to true if using source-maps in production
terserOptions: {
mangle: false,
keep_classnames: true,
keep_fnames: true
}
})
];
}
gulp.task('webpack', () => {
return webpackStream(config)
.pipe(gulp.dest('_output/UI'));
});
gulp.task('webpackWatch', () => {
config.watch = true;
return webpackStream(config, webpack)
.on('error', errorHandler)
.pipe(gulp.dest('_output/UI'))
.on('error', errorHandler)
.pipe(livereload())
.on('error', errorHandler);
});

View File

@@ -1,32 +1,23 @@
const reload = require('require-nocache')(module);
const cssVarsFiles = [
'./src/Styles/Variables/colors',
'./src/Styles/Variables/dimensions',
'./src/Styles/Variables/fonts',
'./src/Styles/Variables/animations',
'./src/Styles/Variables/zIndexes'
].map(require.resolve);
module.exports = (ctx, configPath, options) => {
const config = {
plugins: {
'postcss-mixins': {
mixinsDir: [
'frontend/src/Styles/Mixins'
]
},
'postcss-simple-vars': {
variables: () =>
ctx.options.cssVarsFiles.reduce((acc, vars) => {
return Object.assign(acc, reload(vars));
}, {})
},
'postcss-color-function': {},
'postcss-nested': {}
}
};
const mixinsFiles = [
'frontend/src/Styles/Mixins/cover.css',
'frontend/src/Styles/Mixins/linkOverlay.css',
'frontend/src/Styles/Mixins/scroller.css',
'frontend/src/Styles/Mixins/truncate.css'
];
module.exports = {
plugins: [
['postcss-mixins', {
mixinsFiles
}],
['postcss-simple-vars', {
variables: () =>
cssVarsFiles.reduce((acc, vars) => {
return Object.assign(acc, reload(vars));
}, {})
}],
'postcss-color-function',
'postcss-nested'
]
};
return config;
};

View File

@@ -32,8 +32,6 @@ class Queue extends Component {
constructor(props, context) {
super(props, context);
this._shouldBlockRefresh = false;
this.state = {
allSelected: false,
allUnselected: false,
@@ -45,14 +43,6 @@ class Queue extends Component {
};
}
shouldComponentUpdate() {
if (this._shouldBlockRefresh) {
return false;
}
return true;
}
componentDidUpdate(prevProps) {
const {
items,
@@ -95,10 +85,6 @@ class Queue extends Component {
//
// Listeners
onQueueRowModalOpenOrClose = (isOpen) => {
this._shouldBlockRefresh = isOpen;
}
onSelectAllChange = ({ value }) => {
this.setState(selectAll(this.state.selectedState, value));
}
@@ -114,19 +100,15 @@ class Queue extends Component {
}
onRemoveSelectedPress = () => {
this.setState({ isConfirmRemoveModalOpen: true }, () => {
this._shouldBlockRefresh = true;
});
this.setState({ isConfirmRemoveModalOpen: true });
}
onRemoveSelectedConfirmed = (payload) => {
this._shouldBlockRefresh = false;
this.props.onRemoveSelectedPress({ ids: this.getSelectedIds(), ...payload });
this.setState({ isConfirmRemoveModalOpen: false });
}
onConfirmRemoveModalClose = () => {
this._shouldBlockRefresh = false;
this.setState({ isConfirmRemoveModalOpen: false });
}
@@ -226,7 +208,7 @@ class Queue extends Component {
}
{
isAllPopulated && !hasError && !items.length &&
isPopulated && !hasError && !items.length &&
<div>
{translate('QueueIsEmpty')}
</div>
@@ -255,7 +237,6 @@ class Queue extends Component {
columns={columns}
{...item}
onSelectedChange={this.onSelectedChange}
onQueueRowModalOpenOrClose={this.onQueueRowModalOpenOrClose}
/>
);
})

View File

@@ -43,7 +43,6 @@ class QueueConnector extends Component {
const {
useCurrentPage,
fetchQueue,
fetchQueueStatus,
gotoQueueFirstPage
} = this.props;
@@ -54,8 +53,6 @@ class QueueConnector extends Component {
} else {
gotoQueueFirstPage();
}
fetchQueueStatus();
}
componentDidUpdate(prevProps) {
@@ -155,7 +152,6 @@ QueueConnector.propTypes = {
useCurrentPage: PropTypes.bool.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
fetchQueue: PropTypes.func.isRequired,
fetchQueueStatus: PropTypes.func.isRequired,
gotoQueueFirstPage: PropTypes.func.isRequired,
gotoQueuePreviousPage: PropTypes.func.isRequired,
gotoQueueNextPage: PropTypes.func.isRequired,

View File

@@ -43,32 +43,19 @@ class QueueRow extends Component {
}
onRemoveQueueItemModalConfirmed = (blacklist) => {
const {
onRemoveQueueItemPress,
onQueueRowModalOpenOrClose
} = this.props;
onQueueRowModalOpenOrClose(false);
onRemoveQueueItemPress(blacklist);
this.props.onRemoveQueueItemPress(blacklist);
this.setState({ isRemoveQueueItemModalOpen: false });
}
onRemoveQueueItemModalClose = () => {
this.props.onQueueRowModalOpenOrClose(false);
this.setState({ isRemoveQueueItemModalOpen: false });
}
onInteractiveImportPress = () => {
this.props.onQueueRowModalOpenOrClose(true);
this.setState({ isInteractiveImportModalOpen: true });
}
onInteractiveImportModalClose = () => {
this.props.onQueueRowModalOpenOrClose(false);
this.setState({ isInteractiveImportModalOpen: false });
}
@@ -372,8 +359,7 @@ QueueRow.propTypes = {
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
onSelectedChange: PropTypes.func.isRequired,
onGrabPress: PropTypes.func.isRequired,
onRemoveQueueItemPress: PropTypes.func.isRequired,
onQueueRowModalOpenOrClose: PropTypes.func.isRequired
onRemoveQueueItemPress: PropTypes.func.isRequired
};
QueueRow.defaultProps = {

View File

@@ -81,8 +81,7 @@ class AddNewMovie extends Component {
const {
error,
items,
hasExistingMovies,
colorImpairedMode
hasExistingMovies
} = this.props;
const term = this.state.term;
@@ -142,7 +141,6 @@ class AddNewMovie extends Component {
return (
<AddNewMovieSearchResultConnector
key={item.tmdbId}
colorImpairedMode={colorImpairedMode}
{...item}
/>
);
@@ -215,8 +213,7 @@ AddNewMovie.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired,
hasExistingMovies: PropTypes.bool.isRequired,
onMovieLookupChange: PropTypes.func.isRequired,
onClearMovieLookup: PropTypes.func.isRequired,
colorImpairedMode: PropTypes.bool.isRequired
onClearMovieLookup: PropTypes.func.isRequired
};
export default AddNewMovie;

View File

@@ -3,10 +3,8 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { clearAddMovie, lookupMovie } from 'Store/Actions/addMovieActions';
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
import { fetchImportExclusions } from 'Store/Actions/Settings/importExclusions';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import parseUrl from 'Utilities/String/parseUrl';
import AddNewMovie from './AddNewMovie';
@@ -15,15 +13,13 @@ function createMapStateToProps() {
(state) => state.addMovie,
(state) => state.movies.items.length,
(state) => state.router.location,
createUISettingsSelector(),
(addMovie, existingMoviesCount, location, uiSettings) => {
(addMovie, existingMoviesCount, location) => {
const { params } = parseUrl(location.search);
return {
...addMovie,
term: params.term,
hasExistingMovies: existingMoviesCount > 0,
colorImpairedMode: uiSettings.enableColorImpairedMode
hasExistingMovies: existingMoviesCount > 0
};
}
);
@@ -33,9 +29,7 @@ const mapDispatchToProps = {
lookupMovie,
clearAddMovie,
fetchRootFolders,
fetchImportExclusions,
fetchQueueDetails,
clearQueueDetails
fetchImportExclusions
};
class AddNewMovieConnector extends Component {
@@ -52,7 +46,6 @@ class AddNewMovieConnector extends Component {
componentDidMount() {
this.props.fetchRootFolders();
this.props.fetchImportExclusions();
this.props.fetchQueueDetails();
}
componentWillUnmount() {
@@ -61,7 +54,6 @@ class AddNewMovieConnector extends Component {
}
this.props.clearAddMovie();
this.props.clearQueueDetails();
}
//
@@ -110,9 +102,7 @@ AddNewMovieConnector.propTypes = {
lookupMovie: PropTypes.func.isRequired,
clearAddMovie: PropTypes.func.isRequired,
fetchRootFolders: PropTypes.func.isRequired,
fetchImportExclusions: PropTypes.func.isRequired,
fetchQueueDetails: PropTypes.func.isRequired,
clearQueueDetails: PropTypes.func.isRequired
fetchImportExclusions: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(AddNewMovieConnector);

View File

@@ -27,11 +27,9 @@
}
.poster {
position: relative;
display: block;
flex: 0 0 170px;
margin-right: 20px;
height: 250px;
background-color: $defaultColor;
}
.content {
@@ -88,28 +86,6 @@
pointer-events: all;
}
.posterContainer {
position: relative;
}
.certification {
margin-left: 2px;
padding: 0 5px;
border: 1px solid;
border-radius: 5px;
font-size: 16px;
}
.runtime {
margin-left: 8px;
font-size: 16px;
}
.statusContainer {
margin-right: 22px;
font-weight: bold;
}
@media only screen and (max-width: $breakpointMedium) {
.titleRow {
justify-content: space-between;

View File

@@ -7,10 +7,7 @@ import Link from 'Components/Link/Link';
import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
import MovieStatusLabel from 'Movie/Details/MovieStatusLabel';
import MovieIndexProgressBar from 'Movie/Index/ProgressBar/MovieIndexProgressBar';
import MoviePoster from 'Movie/MoviePoster';
import formatRuntime from 'Utilities/Date/formatRuntime';
import translate from 'Utilities/String/translate';
import AddNewMovieModal from './AddNewMovieModal';
import styles from './AddNewMovieSearchResult.css';
@@ -68,17 +65,7 @@ class AddNewMovieSearchResult extends Component {
images,
isExistingMovie,
isExclusionMovie,
isSmallScreen,
colorImpairedMode,
id,
monitored,
hasFile,
isAvailable,
queueStatus,
queueState,
runtime,
movieRuntimeFormat,
certification
isSmallScreen
} = this.props;
const {
@@ -98,30 +85,12 @@ class AddNewMovieSearchResult extends Component {
{
isSmallScreen ?
null :
<div>
<div className={styles.posterContainer}>
<MoviePoster
className={styles.poster}
images={images}
size={250}
overflow={true}
/>
</div>
{
isExistingMovie &&
<MovieIndexProgressBar
monitored={monitored}
hasFile={hasFile}
status={status}
posterWidth={167}
detailedProgressBar={true}
queueStatus={queueStatus}
queueState={queueState}
isAvailable={isAvailable}
/>
}
</div>
<MoviePoster
className={styles.poster}
images={images}
size={250}
overflow={true}
/>
}
<div className={styles.content}>
@@ -164,22 +133,6 @@ class AddNewMovieSearchResult extends Component {
</div>
</div>
<div>
{
!!certification &&
<span className={styles.certification}>
{certification}
</span>
}
{
!!runtime &&
<span className={styles.runtime}>
{formatRuntime(runtime, movieRuntimeFormat)}
</span>
}
</div>
<div>
<Label size={sizes.LARGE}>
<HeartRating
@@ -223,15 +176,13 @@ class AddNewMovieSearchResult extends Component {
/>
{
isExistingMovie && isSmallScreen &&
<MovieStatusLabel
hasMovieFiles={hasFile}
monitored={monitored}
isAvailable={isAvailable}
id={id}
useLabel={true}
colorImpairedMode={colorImpairedMode}
/>
status === 'ended' &&
<Label
kind={kinds.DANGER}
size={sizes.LARGE}
>
Ended
</Label>
}
</div>
@@ -271,18 +222,7 @@ AddNewMovieSearchResult.propTypes = {
images: PropTypes.arrayOf(PropTypes.object).isRequired,
isExistingMovie: PropTypes.bool.isRequired,
isExclusionMovie: PropTypes.bool.isRequired,
isSmallScreen: PropTypes.bool.isRequired,
id: PropTypes.number,
queueItems: PropTypes.arrayOf(PropTypes.object),
monitored: PropTypes.bool.isRequired,
hasFile: PropTypes.bool.isRequired,
isAvailable: PropTypes.bool.isRequired,
colorImpairedMode: PropTypes.bool,
queueStatus: PropTypes.string,
queueState: PropTypes.string,
runtime: PropTypes.number.isRequired,
movieRuntimeFormat: PropTypes.string.isRequired,
certification: PropTypes.string
isSmallScreen: PropTypes.bool.isRequired
};
export default AddNewMovieSearchResult;

View File

@@ -10,17 +10,11 @@ function createMapStateToProps() {
createExistingMovieSelector(),
createExclusionMovieSelector(),
createDimensionsSelector(),
(state) => state.queue.details.items,
(state, { internalId }) => internalId,
(isExistingMovie, isExclusionMovie, dimensions, queueItems, internalId) => {
const firstQueueItem = queueItems.find((q) => q.movieId === internalId && internalId > 0);
(isExistingMovie, isExclusionMovie, dimensions) => {
return {
isExistingMovie,
isExclusionMovie,
isSmallScreen: dimensions.isSmallScreen,
queueStatus: firstQueueItem ? firstQueueItem.status : null,
queueState: firstQueueItem ? firstQueueItem.trackedDownloadState : null
isSmallScreen: dimensions.isSmallScreen
};
}
);

View File

@@ -11,47 +11,9 @@ import UpdateChanges from 'System/Updates/UpdateChanges';
import translate from 'Utilities/String/translate';
import styles from './AppUpdatedModalContent.css';
function mergeUpdates(items, version, prevVersion) {
let installedIndex = items.findIndex((u) => u.version === version);
let installedPreviouslyIndex = items.findIndex((u) => u.version === prevVersion);
if (installedIndex === -1) {
installedIndex = 0;
}
if (installedPreviouslyIndex === -1) {
installedPreviouslyIndex = items.length;
} else if (installedPreviouslyIndex === installedIndex && items.length) {
installedPreviouslyIndex += 1;
}
const appliedUpdates = items.slice(installedIndex, installedPreviouslyIndex);
if (!appliedUpdates.length) {
return null;
}
const appliedChanges = { new: [], fixed: [] };
appliedUpdates.forEach((u) => {
if (u.changes) {
appliedChanges.new.push(... u.changes.new);
appliedChanges.fixed.push(... u.changes.fixed);
}
});
const mergedUpdate = Object.assign({}, appliedUpdates[0], { changes: appliedChanges });
if (!appliedChanges.new.length && !appliedChanges.fixed.length) {
mergedUpdate.changes = null;
}
return mergedUpdate;
}
function AppUpdatedModalContent(props) {
const {
version,
prevVersion,
isPopulated,
error,
items,
@@ -59,7 +21,7 @@ function AppUpdatedModalContent(props) {
onModalClose
} = props;
const update = mergeUpdates(items, version, prevVersion);
const update = items[0];
return (
<ModalContent onModalClose={onModalClose}>
@@ -127,7 +89,6 @@ function AppUpdatedModalContent(props) {
AppUpdatedModalContent.propTypes = {
version: PropTypes.string.isRequired,
prevVersion: PropTypes.string,
isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,

View File

@@ -8,9 +8,8 @@ import AppUpdatedModalContent from './AppUpdatedModalContent';
function createMapStateToProps() {
return createSelector(
(state) => state.app.version,
(state) => state.app.prevVersion,
(state) => state.system.updates,
(version, prevVersion, updates) => {
(version, updates) => {
const {
isPopulated,
error,
@@ -19,7 +18,6 @@ function createMapStateToProps() {
return {
version,
prevVersion,
isPopulated,
error,
items

View File

@@ -29,7 +29,7 @@
}
.time {
flex: 0 0 125px;
flex: 0 0 120px;
margin-right: 10px;
border: none !important;
}

View File

@@ -24,7 +24,7 @@ function createMissingMovieIdsSelector() {
const inCinemas = movie.inCinemas;
if (
!movie.hasFile &&
!movie.movieFileId &&
moment(inCinemas).isAfter(start) &&
moment(inCinemas).isBefore(end) &&
isBefore(movie.inCinemas) &&

View File

@@ -22,7 +22,7 @@ function getUrls(state) {
tags
} = state;
let icalUrl = `${window.location.host}${window.Radarr.urlBase}/feed/v3/calendar/Radarr.ics?`;
let icalUrl = `${window.location.host}${window.Radarr.urlBase}/feed/calendar/Radarr.ics?`;
if (unmonitored) {
icalUrl += 'unmonitored=true&';

View File

@@ -16,7 +16,7 @@ function createTagListSelector() {
(selectedFilterBuilderProp.type === filterBuilderTypes.NUMBER ||
selectedFilterBuilderProp.type === filterBuilderTypes.STRING) &&
filterType !== filterTypes.EQUAL &&
filterType !== filterTypes.NOT_EQUAL ||
filterType !== filterBuilderTypes.NOT_EQUAL ||
!selectedFilterBuilderProp.optionsSelector
) {
return [];

View File

@@ -85,21 +85,3 @@
display: inline-block;
margin: 5px -5px 5px 0;
}
.mobileCloseButtonContainer {
display: flex;
justify-content: flex-end;
height: 40px;
border-bottom: 1px solid $borderColor;
}
.mobileCloseButton {
width: 40px;
height: 40px;
text-align: center;
line-height: 40px;
&:hover {
color: $modalCloseButtonHoverColor;
}
}

View File

@@ -479,7 +479,6 @@ class EnhancedSelectInput extends Component {
<OptionComponent
key={v.key}
id={v.key}
dividerAfter={v.dividerAfter}
depth={depth}
isSelected={isSelectedItem(index, this.props)}
isDisabled={parentSelected}
@@ -519,18 +518,6 @@ class EnhancedSelectInput extends Component {
scrollDirection={scrollDirections.NONE}
>
<Scroller className={styles.optionsModalScroller}>
<div className={styles.mobileCloseButtonContainer}>
<Link
className={styles.mobileCloseButton}
onPress={this.onOptionsModalClose}
>
<Icon
name={icons.CLOSE}
size={18}
/>
</Link>
</div>
{
values.map((v, index) => {
const hasParent = v.parentKey !== undefined;
@@ -540,7 +527,6 @@ class EnhancedSelectInput extends Component {
<OptionComponent
key={v.key}
id={v.key}
dividerAfter={v.dividerAfter}
depth={depth}
isSelected={isSelectedItem(index, this.props)}
isMultiSelect={isMultiSelect}

View File

@@ -54,8 +54,4 @@
&:last-child {
border: none;
}
&:hover {
background-color: unset;
}
}

View File

@@ -12,9 +12,7 @@ class EnhancedSelectInputOption extends Component {
//
// Listeners
onPress = (e) => {
e.preventDefault();
onPress = () => {
const {
id,
onSelect

View File

@@ -13,7 +13,6 @@ import EnhancedSelectInputConnector from './EnhancedSelectInputConnector';
import FormInputHelpText from './FormInputHelpText';
import IndexerFlagsSelectInputConnector from './IndexerFlagsSelectInputConnector';
import KeyValueListInput from './KeyValueListInput';
import LanguageSelectInputConnector from './LanguageSelectInputConnector';
import MovieMonitoredSelectInput from './MovieMonitoredSelectInput';
import NumberInput from './NumberInput';
import OAuthInputConnector from './OAuthInputConnector';
@@ -73,9 +72,6 @@ function getComponent(type) {
case inputTypes.INDEXER_FLAGS_SELECT:
return IndexerFlagsSelectInputConnector;
case inputTypes.LANGUAGE_SELECT:
return LanguageSelectInputConnector;
case inputTypes.SELECT:
return EnhancedSelectInput;

View File

@@ -21,8 +21,3 @@
color: $darkGray;
font-size: $smallFontSize;
}
.divider {
border: none;
border-bottom: 1px solid $lightGray;
}

View File

@@ -12,46 +12,37 @@ function HintedSelectInputOption(props) {
depth,
isSelected,
isDisabled,
dividerAfter,
isMultiSelect,
isMobile,
...otherProps
} = props;
return (
<div>
<EnhancedSelectInputOption
id={id}
depth={depth}
isSelected={isSelected}
isDisabled={isDisabled}
isHidden={isDisabled}
isMultiSelect={isMultiSelect}
isMobile={isMobile}
{...otherProps}
<EnhancedSelectInputOption
id={id}
depth={depth}
isSelected={isSelected}
isDisabled={isDisabled}
isHidden={isDisabled}
isMultiSelect={isMultiSelect}
isMobile={isMobile}
{...otherProps}
>
<div className={classNames(
styles.optionText,
isMobile && styles.isMobile
)}
>
<div className={classNames(
styles.optionText,
isMobile && styles.isMobile
)}
>
<div>{value}</div>
<div>{value}</div>
{
hint != null &&
<div className={styles.hintText}>
{hint}
</div>
}
</div>
</EnhancedSelectInputOption>
{
dividerAfter ?
<div className={styles.divider} /> :
null
}
</div>
{
hint != null &&
<div className={styles.hintText}>
{hint}
</div>
}
</div>
</EnhancedSelectInputOption>
);
}
@@ -59,18 +50,15 @@ HintedSelectInputOption.propTypes = {
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
value: PropTypes.string.isRequired,
hint: PropTypes.node,
name: PropTypes.string,
depth: PropTypes.number,
isSelected: PropTypes.bool.isRequired,
isDisabled: PropTypes.bool.isRequired,
dividerAfter: PropTypes.bool.isRequired,
isMultiSelect: PropTypes.bool.isRequired,
isMobile: PropTypes.bool.isRequired
};
HintedSelectInputOption.defaultProps = {
isDisabled: false,
dividerAfter: false,
isHidden: false,
isMultiSelect: false
};

View File

@@ -1,52 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import EnhancedSelectInput from './EnhancedSelectInput';
function createMapStateToProps() {
return createSelector(
(state, { values }) => values,
( languages ) => {
const minId = languages.reduce((min, v) => (v.key < 1 ? v.key : min), languages[0].key);
const values = languages.map(({ key, value }) => {
return {
key,
value,
dividerAfter: minId < 1 ? key === minId : false
};
});
return {
values
};
}
);
}
class LanguageSelectInputConnector extends Component {
//
// Render
render() {
return (
<EnhancedSelectInput
{...this.props}
onChange={this.props.onChange}
/>
);
}
}
LanguageSelectInputConnector.propTypes = {
name: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.number), PropTypes.number]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
onChange: PropTypes.func.isRequired
};
export default connect(createMapStateToProps)(LanguageSelectInputConnector);

View File

@@ -49,7 +49,6 @@ function getSelectValues(selectOptions) {
result.push({
key: option.value,
value: option.name,
dividerAfter: option.dividerAfter,
hint: option.hint
});

View File

@@ -10,16 +10,13 @@ const ADD_NEW_KEY = 'addNew';
function createMapStateToProps() {
return createSelector(
(state) => state.rootFolders,
(state, { value }) => value,
(state, { includeMissingValue }) => includeMissingValue,
(state, { includeNoChange }) => includeNoChange,
(rootFolders, value, includeMissingValue, includeNoChange) => {
(rootFolders, includeNoChange) => {
const values = rootFolders.items.map((rootFolder) => {
return {
key: rootFolder.path,
value: rootFolder.path,
freeSpace: rootFolder.freeSpace,
isMissing: false
freeSpace: rootFolder.freeSpace
};
});
@@ -27,8 +24,7 @@ function createMapStateToProps() {
values.unshift({
key: 'noChange',
value: 'No Change',
isDisabled: true,
isMissing: false
isDisabled: true
});
}
@@ -41,15 +37,6 @@ function createMapStateToProps() {
});
}
if (includeMissingValue && !values.find((v) => v.key === value)) {
values.push({
key: value,
value,
isMissing: true,
isDisabled: true
});
}
values.push({
key: ADD_NEW_KEY,
value: 'Add a new path'
@@ -164,8 +151,7 @@ RootFolderSelectInputConnector.propTypes = {
};
RootFolderSelectInputConnector.defaultProps = {
includeNoChange: false,
value: ''
includeNoChange: false
};
export default connect(createMapStateToProps, createMapDispatchToProps)(RootFolderSelectInputConnector);

View File

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

View File

@@ -10,7 +10,6 @@ function RootFolderSelectInputOption(props) {
id,
value,
freeSpace,
isMissing,
movieFolder,
isMobile,
isWindows,
@@ -44,20 +43,11 @@ function RootFolderSelectInputOption(props) {
</div>
{
freeSpace == null ?
null :
freeSpace != null &&
<div className={styles.freeSpace}>
{formatBytes(freeSpace)} Free
</div>
}
{
isMissing ?
<div className={styles.isMissing}>
Missing
</div> :
null
}
</div>
</EnhancedSelectInputOption>
);
@@ -68,7 +58,6 @@ RootFolderSelectInputOption.propTypes = {
value: PropTypes.string.isRequired,
freeSpace: PropTypes.number,
movieFolder: PropTypes.string,
isMissing: PropTypes.boolean,
isMobile: PropTypes.bool.isRequired,
isWindows: PropTypes.bool
};

View File

@@ -19,10 +19,6 @@
&.outline {
color: $dangerColor;
}
&:global(.colorImpaired) {
background: repeating-linear-gradient(90deg, color($dangerColor shade(5%)), color($dangerColor shade(5%)) 5px, color($dangerColor shade(15%)) 5px, color($dangerColor shade(15%)) 10px);
}
}
.default {
@@ -89,10 +85,6 @@
&.outline {
color: $warningColor;
}
&:global(.colorImpaired) {
background: repeating-linear-gradient(45deg, $warningColor, $warningColor 5px, color($warningColor tint(15%)) 5px, color($warningColor tint(15%)) 10px);
}
}
.queue {

View File

@@ -11,7 +11,6 @@ function Label(props) {
size,
outline,
children,
colorImpairedMode,
...otherProps
} = props;
@@ -21,8 +20,7 @@ function Label(props) {
className,
styles[kind],
styles[size],
outline && styles.outline,
colorImpairedMode && 'colorImpaired'
outline && styles.outline
)}
{...otherProps}
>
@@ -36,16 +34,14 @@ Label.propTypes = {
kind: PropTypes.oneOf(kinds.all).isRequired,
size: PropTypes.oneOf(sizes.all).isRequired,
outline: PropTypes.bool.isRequired,
children: PropTypes.node.isRequired,
colorImpairedMode: PropTypes.bool
children: PropTypes.node.isRequired
};
Label.defaultProps = {
className: styles.label,
kind: kinds.DEFAULT,
size: sizes.SMALL,
outline: false,
colorImpairedMode: false
outline: false
};
export default Label;

View File

@@ -38,12 +38,11 @@ class Link extends Component {
const linkProps = { target };
let el = component;
if (to && typeof to === 'string') {
if (to) {
if ((/\w+?:\/\//).test(to)) {
el = 'a';
linkProps.href = to;
linkProps.target = target || '_blank';
linkProps.rel = 'noreferrer';
} else if (noRouter) {
el = 'a';
linkProps.href = to;
@@ -53,18 +52,6 @@ class Link extends Component {
linkProps.to = `${window.Radarr.urlBase}/${to.replace(/^\//, '')}`;
linkProps.target = target;
}
} else if (to && typeof to === 'object') {
el = RouterLink;
linkProps.target = target;
if (to.pathname.startsWith(`${window.Radarr.urlBase}/`)) {
linkProps.to = to;
} else {
const pathname = `${window.Radarr.urlBase}/${to.pathname.replace(/^\//, '')}`;
linkProps.to = {
...to,
pathname
};
}
}
if (el === 'button' || el === 'input') {
@@ -95,7 +82,7 @@ class Link extends Component {
Link.propTypes = {
className: PropTypes.string,
component: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
to: PropTypes.string,
target: PropTypes.string,
isDisabled: PropTypes.bool,
noRouter: PropTypes.bool,

View File

@@ -1,5 +1,5 @@
.loadingMessage {
margin: 10px 10px 0;
margin: 50px 10px 0;
text-align: center;
font-weight: 300;
font-size: 36px;

View File

@@ -12,7 +12,7 @@ const messages = [
'Hum something loud while others stare',
'Loading humorous message... Please Wait',
'I could\'ve been faster in Python',
'Don\'t forget to rewind your movies',
'Don\'t forget to rewind your tracks',
'Congratulations! you are the 1000th visitor.',
'HELP! I\'m being held hostage and forced to write these stupid lines!',
'RE-calibrating the internet...',

View File

@@ -30,10 +30,10 @@ function ConfirmModal(props) {
useEffect(() => {
if (isOpen) {
bindShortcut('enter', onConfirm);
return () => unbindShortcut('enter', onConfirm);
} else {
unbindShortcut('enter', onConfirm);
}
}, [isOpen, onConfirm]);
}, [onConfirm]);
return (
<Modal

View File

@@ -53,13 +53,7 @@ class PageHeader extends Component {
return (
<div className={styles.header}>
<div className={styles.logoContainer}>
<Link
className={styles.logoLink}
to={{
pathname: '/',
state: { restoreScrollPosition: true }
}}
>
<Link to={'/'}>
<img
className={isSmallScreen ? styles.logo : styles.logoFull}
src={isSmallScreen ? `${window.Radarr.urlBase}/Content/Images/logo.png` : `${window.Radarr.urlBase}/Content/Images/logo-full.png`}

View File

@@ -1,11 +1,3 @@
.page {
composes: page from '~./Page.css';
}
.logoFull {
margin-top: 50px;
margin-right: auto;
margin-left: auto;
width: 120px;
height: 40px;
}

File diff suppressed because one or more lines are too long

View File

@@ -10,7 +10,6 @@ import SpinnerIcon from 'Components/SpinnerIcon';
import { forEach } from 'Helpers/elementChildren';
import { align, icons } from 'Helpers/Props';
import dimensions from 'Styles/Variables/dimensions';
import translate from 'Utilities/String/translate';
import styles from './PageToolbarSection.css';
const BUTTON_WIDTH = parseInt(dimensions.toolbarButtonWidth);
@@ -162,7 +161,7 @@ class PageToolbarSection extends Component {
<ToolbarMenuButton
className={styles.overflowMenuButton}
iconName={icons.OVERFLOW}
text={translate('More')}
text="More"
/>
<MenuContent>

View File

@@ -1,8 +1,8 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { DndProvider } from 'react-dnd-multi-backend';
import HTML5toTouch from 'react-dnd-multi-backend/dist/esm/HTML5toTouch';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -128,7 +128,7 @@ class TableOptionsModal extends Component {
const isDraggingDown = isDragging && dropIndex > dragIndex;
return (
<DndProvider options={HTML5toTouch}>
<DndProvider backend={HTML5Backend}>
<Modal
isOpen={isOpen}
onModalClose={onModalClose}

View File

@@ -39,8 +39,7 @@ class VirtualTable extends Component {
super(props, context);
this.state = {
width: 0,
scrollRestored: false
width: 0
};
this._grid = null;
@@ -49,13 +48,11 @@ class VirtualTable extends Component {
componentDidUpdate(prevProps, prevState) {
const {
items,
scrollIndex,
scrollTop
scrollIndex
} = this.props;
const {
width,
scrollRestored
width
} = this.state;
if (this._grid &&
@@ -71,11 +68,6 @@ class VirtualTable extends Component {
columnIndex: 0
});
}
if (this._grid && scrollTop !== undefined && scrollTop !== 0 && !scrollRestored) {
this.setState({ scrollRestored: true });
this._grid.scrollToPosition({ scrollTop });
}
}
//
@@ -104,7 +96,6 @@ class VirtualTable extends Component {
items,
scroller,
focusScroller,
scrollTop: ignored,
header,
headerHeight,
rowRenderer,
@@ -189,7 +180,6 @@ VirtualTable.propTypes = {
className: PropTypes.string.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
scrollIndex: PropTypes.number,
scrollTop: PropTypes.number,
scroller: PropTypes.instanceOf(Element).isRequired,
focusScroller: PropTypes.bool.isRequired,
header: PropTypes.node.isRequired,

View File

@@ -77,10 +77,8 @@ function keyboardShortcuts(WrappedComponent) {
}
unbindShortcut = (key) => {
if (this._mousetrap != null) {
delete this._mousetrapBindings[key];
this._mousetrap.unbind(key);
}
delete this._mousetrapBindings[key];
this._mousetrap.unbind(key);
}
unbindAllShortcuts = () => {

View File

@@ -8,7 +8,7 @@ function withScrollPosition(WrappedComponent, scrollPositionKey) {
history
} = props;
const scrollTop = history.action === 'POP' || (history.location.state && history.location.state.restoreScrollPosition) ?
const scrollTop = history.action === 'POP' ?
scrollPositions[scrollPositionKey] :
0;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -184,7 +184,6 @@ export const PAGE_LAST = fasFastForward;
export const PARENT = fasLevelUpAlt;
export const PAUSED = fasPause;
export const PENDING = farClock;
export const PLAY = fasPlay;
export const PROFILE = fasUser;
export const POSTER = fasTh;
export const QUEUED = fasCloud;

View File

@@ -3,7 +3,6 @@ export const AVAILABILITY_SELECT = 'availabilitySelect';
export const CAPTCHA = 'captcha';
export const CHECK = 'check';
export const DEVICE = 'device';
export const KEY_VALUE_LIST = 'keyValueList';
export const MOVIE_MONITORED_SELECT = 'movieMonitoredSelect';
export const NUMBER = 'number';
export const OAUTH = 'oauth';
@@ -12,7 +11,6 @@ export const PATH = 'path';
export const QUALITY_PROFILE_SELECT = 'qualityProfileSelect';
export const ROOT_FOLDER_SELECT = 'rootFolderSelect';
export const INDEXER_FLAGS_SELECT = 'indexerFlagsSelect';
export const LANGUAGE_SELECT = 'languageSelect';
export const SELECT = 'select';
export const DYNAMIC_SELECT = 'dynamicSelect';
export const TAG = 'tag';
@@ -28,7 +26,6 @@ export const all = [
CAPTCHA,
CHECK,
DEVICE,
KEY_VALUE_LIST,
MOVIE_MONITORED_SELECT,
NUMBER,
OAUTH,
@@ -37,7 +34,6 @@ export const all = [
QUALITY_PROFILE_SELECT,
ROOT_FOLDER_SELECT,
INDEXER_FLAGS_SELECT,
LANGUAGE_SELECT,
SELECT,
DYNAMIC_SELECT,
TAG,

View File

@@ -1,6 +1,5 @@
export const DANGER = 'danger';
export const DEFAULT = 'default';
export const DELETE = 'delete';
export const DISABLED = 'disabled';
export const INFO = 'info';
export const INVERSE = 'inverse';
@@ -14,7 +13,6 @@ export const QUEUE = 'queue';
export const all = [
DANGER,
DEFAULT,
DELETE,
DISABLED,
INFO,
INVERSE,

View File

@@ -272,7 +272,7 @@ class InteractiveImportModalContent extends Component {
isPopulated && !!items.length && !isFetching && !isFetching &&
<Table
columns={columns}
horizontalScroll={true}
horizontalScroll={false}
selectAll={true}
allSelected={allSelected}
allUnselected={allUnselected}

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { reprocessInteractiveImportItems, updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
import { updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
import { fetchLanguages } from 'Store/Actions/settingsActions';
import SelectLanguageModalContent from './SelectLanguageModalContent';
@@ -33,7 +33,6 @@ function createMapStateToProps() {
const mapDispatchToProps = {
dispatchFetchLanguages: fetchLanguages,
dispatchReprocessInteractiveImportItems: reprocessInteractiveImportItems,
dispatchUpdateInteractiveImportItems: updateInteractiveImportItems
};
@@ -52,12 +51,6 @@ class SelectLanguageModalContentConnector extends Component {
// Listeners
onLanguageSelect = ({ languageIds }) => {
const {
ids,
dispatchUpdateInteractiveImportItems,
dispatchReprocessInteractiveImportItems
} = this.props;
const languages = [];
languageIds.forEach((languageId) => {
@@ -69,13 +62,11 @@ class SelectLanguageModalContentConnector extends Component {
}
});
dispatchUpdateInteractiveImportItems({
ids,
this.props.dispatchUpdateInteractiveImportItems({
ids: this.props.ids,
languages
});
dispatchReprocessInteractiveImportItems({ ids });
this.props.onModalClose(true);
}
@@ -100,7 +91,6 @@ SelectLanguageModalContentConnector.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired,
dispatchFetchLanguages: PropTypes.func.isRequired,
dispatchUpdateInteractiveImportItems: PropTypes.func.isRequired,
dispatchReprocessInteractiveImportItems: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { reprocessInteractiveImportItems, updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
import { updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
import { fetchQualityProfileSchema } from 'Store/Actions/settingsActions';
import getQualities from 'Utilities/Quality/getQualities';
import SelectQualityModalContent from './SelectQualityModalContent';
@@ -31,7 +31,6 @@ function createMapStateToProps() {
const mapDispatchToProps = {
dispatchFetchQualityProfileSchema: fetchQualityProfileSchema,
dispatchReprocessInteractiveImportItems: reprocessInteractiveImportItems,
dispatchUpdateInteractiveImportItems: updateInteractiveImportItems
};
@@ -50,12 +49,6 @@ class SelectQualityModalContentConnector extends Component {
// Listeners
onQualitySelect = ({ qualityId, proper, real }) => {
const {
ids,
dispatchUpdateInteractiveImportItems,
dispatchReprocessInteractiveImportItems
} = this.props;
const quality = _.find(this.props.items,
(item) => item.id === qualityId);
@@ -64,16 +57,14 @@ class SelectQualityModalContentConnector extends Component {
real: real ? 1 : 0
};
dispatchUpdateInteractiveImportItems({
ids,
this.props.dispatchUpdateInteractiveImportItems({
ids: this.props.ids,
quality: {
quality,
revision
}
});
dispatchReprocessInteractiveImportItems({ ids });
this.props.onModalClose(true);
}
@@ -97,7 +88,6 @@ SelectQualityModalContentConnector.propTypes = {
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
dispatchFetchQualityProfileSchema: PropTypes.func.isRequired,
dispatchReprocessInteractiveImportItems: PropTypes.func.isRequired,
dispatchUpdateInteractiveImportItems: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@@ -22,20 +22,6 @@ const columns = [
isSortable: true,
isVisible: true
},
{
name: 'releaseWeight',
label: React.createElement(Icon, { name: icons.DOWNLOAD }),
isSortable: true,
fixedSortDirection: sortDirections.ASCENDING,
isVisible: true
},
{
name: 'rejections',
label: React.createElement(Icon, { name: icons.DANGER }),
isSortable: true,
fixedSortDirection: sortDirections.ASCENDING,
isVisible: true
},
{
name: 'title',
label: translate('Title'),
@@ -99,6 +85,20 @@ const columns = [
label: React.createElement(Icon, { name: icons.FLAG }),
isSortable: true,
isVisible: true
},
{
name: 'rejections',
label: React.createElement(Icon, { name: icons.DANGER }),
isSortable: true,
fixedSortDirection: sortDirections.ASCENDING,
isVisible: true
},
{
name: 'releaseWeight',
label: React.createElement(Icon, { name: icons.DOWNLOAD }),
isSortable: true,
fixedSortDirection: sortDirections.ASCENDING,
isVisible: true
}
];

View File

@@ -145,46 +145,6 @@ class InteractiveSearchRow extends Component {
{formatAge(age, ageHours, ageMinutes)}
</TableRowCell>
<TableRowCell className={styles.download}>
<SpinnerIconButton
name={getDownloadIcon(isGrabbing, isGrabbed, grabError)}
kind={grabError ? kinds.DANGER : kinds.DEFAULT}
title={getDownloadTooltip(isGrabbing, isGrabbed, grabError)}
isDisabled={isGrabbed}
isSpinning={isGrabbing}
onPress={downloadAllowed ? this.onGrabPress : this.onConfirmGrabPress}
/>
</TableRowCell>
<TableRowCell className={styles.rejected}>
{
!!rejections.length &&
<Popover
anchor={
<Icon
name={icons.DANGER}
kind={kinds.DANGER}
/>
}
title={translate('ReleaseRejected')}
body={
<ul>
{
rejections.map((rejection, index) => {
return (
<li key={index}>
{rejection}
</li>
);
})
}
</ul>
}
position={tooltipPositions.BOTTOM}
/>
}
</TableRowCell>
<TableRowCell className={styles.title}>
<Link
to={infoUrl}
@@ -292,11 +252,51 @@ class InteractiveSearchRow extends Component {
}
</ul>
}
position={tooltipPositions.BOTTOM}
position={tooltipPositions.LEFT}
/>
}
</TableRowCell>
<TableRowCell className={styles.rejected}>
{
!!rejections.length &&
<Popover
anchor={
<Icon
name={icons.DANGER}
kind={kinds.DANGER}
/>
}
title={translate('ReleaseRejected')}
body={
<ul>
{
rejections.map((rejection, index) => {
return (
<li key={index}>
{rejection}
</li>
);
})
}
</ul>
}
position={tooltipPositions.LEFT}
/>
}
</TableRowCell>
<TableRowCell className={styles.download}>
<SpinnerIconButton
name={getDownloadIcon(isGrabbing, isGrabbed, grabError)}
kind={grabError ? kinds.DANGER : kinds.DEFAULT}
title={getDownloadTooltip(isGrabbing, isGrabbed, grabError)}
isDisabled={isGrabbed}
isSpinning={isGrabbing}
onPress={downloadAllowed ? this.onGrabPress : this.onConfirmGrabPress}
/>
</TableRowCell>
<ConfirmModal
isOpen={this.state.isConfirmGrabModalOpen}
kind={kinds.WARNING}

View File

@@ -54,18 +54,22 @@ class DeleteMovieModalContent extends Component {
const {
title,
path,
hasFile,
sizeOnDisk,
statistics,
onModalClose
} = this.props;
const {
movieFileCount,
sizeOnDisk
} = statistics;
const deleteFiles = this.state.deleteFiles;
const addImportExclusion = this.state.addImportExclusion;
let deleteFilesLabel = hasFile ? translate('DeleteFileLabel', [1]) : translate('DeleteFilesLabel', [0]);
let deleteFilesLabel = translate('DeleteFilesLabel', [movieFileCount]);
let deleteFilesHelpText = translate('DeleteFilesHelpText');
if (!hasFile) {
if (movieFileCount === 0) {
deleteFilesLabel = translate('DeleteMovieFolderLabel');
deleteFilesHelpText = translate('DeleteMovieFolderHelpText');
}
@@ -88,21 +92,6 @@ class DeleteMovieModalContent extends Component {
{path}
</div>
<FormGroup>
<FormLabel>
{translate('AddListExclusion')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="addImportExclusion"
value={addImportExclusion}
helpText={translate('AddImportExclusionHelpText')}
kind={kinds.DANGER}
onChange={this.onAddImportExclusionChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{deleteFilesLabel}</FormLabel>
@@ -124,14 +113,29 @@ class DeleteMovieModalContent extends Component {
</div>
{
!!hasFile &&
!!movieFileCount &&
<div>
{hasFile} {translate('MovieFilesTotaling')} {formatBytes(sizeOnDisk)}
{movieFileCount} {translate('MovieFilesTotaling')} {formatBytes(sizeOnDisk)}
</div>
}
</div>
}
<FormGroup>
<FormLabel>
{translate('AddListExclusion')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="addImportExclusion"
value={addImportExclusion}
helpText={translate('AddImportExclusionHelpText')}
kind={kinds.DANGER}
onChange={this.onAddImportExclusionChange}
/>
</FormGroup>
</ModalBody>
<ModalFooter>
@@ -154,10 +158,15 @@ class DeleteMovieModalContent extends Component {
DeleteMovieModalContent.propTypes = {
title: PropTypes.string.isRequired,
path: PropTypes.string.isRequired,
hasFile: PropTypes.bool.isRequired,
sizeOnDisk: PropTypes.string.isRequired,
statistics: PropTypes.object.isRequired,
onDeletePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
DeleteMovieModalContent.defaultProps = {
statistics: {
movieFileCount: 0
}
};
export default DeleteMovieModalContent;

View File

@@ -1,7 +1,9 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
import MovieCreditPosters from '../MovieCreditPosters';
import MovieCastPoster from './MovieCastPoster';
@@ -24,8 +26,19 @@ function createMapStateToProps() {
);
}
const mapDispatchToProps = {
fetchRootFolders
};
class MovieCastPostersConnector extends Component {
//
// Lifecycle
componentDidMount() {
this.props.fetchRootFolders();
}
//
// Render
@@ -40,4 +53,8 @@ class MovieCastPostersConnector extends Component {
}
}
export default connect(createMapStateToProps)(MovieCastPostersConnector);
MovieCastPostersConnector.propTypes = {
fetchRootFolders: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(MovieCastPostersConnector);

View File

@@ -1,7 +1,9 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
import MovieCreditPosters from '../MovieCreditPosters';
import MovieCrewPoster from './MovieCrewPoster';
@@ -24,8 +26,19 @@ function createMapStateToProps() {
);
}
const mapDispatchToProps = {
fetchRootFolders
};
class MovieCrewPostersConnector extends Component {
//
// Lifecycle
componentDidMount() {
this.props.fetchRootFolders();
}
//
// Render
@@ -40,4 +53,8 @@ class MovieCrewPostersConnector extends Component {
}
}
export default connect(createMapStateToProps)(MovieCrewPostersConnector);
MovieCrewPostersConnector.propTypes = {
fetchRootFolders: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(MovieCrewPostersConnector);

View File

@@ -286,7 +286,7 @@ class MovieDetails extends Component {
onMonitorTogglePress,
onRefreshPress,
onSearchPress,
queueItems,
queueDetails,
movieRuntimeFormat
} = this.props;
@@ -318,6 +318,7 @@ class MovieDetails extends Component {
<PageToolbarButton
label={translate('SearchMovie')}
iconName={icons.SEARCH}
isDisabled={!monitored}
isSpinning={isSearching}
title={undefined}
onPress={onSearchPress}
@@ -522,7 +523,7 @@ class MovieDetails extends Component {
hasMovieFiles={hasMovieFiles}
monitored={monitored}
isAvailable={isAvailable}
queueItem={(queueItems.length > 0) ? queueItems[0] : null}
queueDetails={queueDetails}
/>
</span>
</InfoLabel>
@@ -793,7 +794,7 @@ MovieDetails.propTypes = {
onRefreshPress: PropTypes.func.isRequired,
onSearchPress: PropTypes.func.isRequired,
onGoToMovie: PropTypes.func.isRequired,
queueItems: PropTypes.arrayOf(PropTypes.object),
queueDetails: PropTypes.object,
movieRuntimeFormat: PropTypes.string.isRequired
};

View File

@@ -89,10 +89,10 @@ function createMapStateToProps() {
createAllMoviesSelector(),
createCommandsSelector(),
createDimensionsSelector(),
(state) => state.queue.details.items,
(state) => state.queue.details,
(state) => state.app.isSidebarVisible,
(state) => state.settings.ui.item.movieRuntimeFormat,
(titleSlug, movieFiles, movieCredits, extraFiles, allMovies, commands, dimensions, queueItems, isSidebarVisible, movieRuntimeFormat) => {
(titleSlug, movieFiles, movieCredits, extraFiles, allMovies, commands, dimensions, queueDetails, isSidebarVisible, movieRuntimeFormat) => {
const sortedMovies = _.orderBy(allMovies, 'sortTitle');
const movieIndex = _.findIndex(sortedMovies, { titleSlug });
const movie = sortedMovies[movieIndex];
@@ -165,7 +165,7 @@ function createMapStateToProps() {
nextMovie,
isSmallScreen: dimensions.isSmallScreen,
isSidebarVisible,
queueItems,
queueDetails,
movieRuntimeFormat
};
}

View File

@@ -41,19 +41,6 @@ function MovieDetailsLinks(props) {
</Label>
</Link>
<Link
className={styles.link}
to={`https://letterboxd.com/tmdb/${tmdbId}`}
>
<Label
className={styles.linkLabel}
kind={kinds.INFO}
size={sizes.LARGE}
>
{translate('Letterboxd')}
</Label>
</Link>
{
!!imdbId &&
<Link
@@ -74,7 +61,7 @@ function MovieDetailsLinks(props) {
!!imdbId &&
<Link
className={styles.link}
to={`https://moviechat.org/${imdbId}/`}
to={` https://moviechat.org/${imdbId}/`}
>
<Label
className={styles.linkLabel}
@@ -90,7 +77,7 @@ function MovieDetailsLinks(props) {
!!youTubeTrailerId &&
<Link
className={styles.link}
to={`https://www.youtube.com/watch?v=${youTubeTrailerId}/`}
to={` https://www.youtube.com/watch?v=${youTubeTrailerId}/`}
>
<Label
className={styles.linkLabel}

View File

@@ -8,7 +8,6 @@ import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import NotFound from 'Components/NotFound';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate';
import MovieDetailsConnector from './MovieDetailsConnector';
@@ -47,8 +46,7 @@ function createMapStateToProps() {
}
const mapDispatchToProps = {
push,
fetchRootFolders
push
};
class MovieDetailsPageConnector extends Component {
@@ -56,10 +54,6 @@ class MovieDetailsPageConnector extends Component {
//
// Lifecycle
componentDidMount() {
this.props.fetchRootFolders();
}
componentDidUpdate(prevProps) {
if (!this.props.titleSlug) {
this.props.push(`${window.Radarr.urlBase}/`);
@@ -118,8 +112,7 @@ MovieDetailsPageConnector.propTypes = {
isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object,
match: PropTypes.shape({ params: PropTypes.shape({ titleSlug: PropTypes.string.isRequired }).isRequired }).isRequired,
push: PropTypes.func.isRequired,
fetchRootFolders: PropTypes.func.isRequired
push: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(MovieDetailsPageConnector);

View File

@@ -1,29 +1,24 @@
.queue {
padding-left: 2px;
border-left: 4px solid $queueColor;
}
.continuing {
padding-left: 2px;
border-left: 4px solid $primaryColor;
}
.availNotMonitored {
padding-left: 2px;
border-left: 4px solid $darkGray;
}
.ended {
padding-left: 2px;
border-left: 4px solid $successColor;
}
.missingMonitored {
.missing {
padding-left: 2px;
border-left: 4px solid $dangerColor;
}
.missingUnmonitored {
.downloaded {
padding-left: 2px;
border-left: 4px solid $successColor;
}
.notAvailable {
padding-left: 2px;
border-left: 4px solid $primaryColor;
}
.unmonitored {
padding-left: 2px;
border-left: 4px solid $warningColor;
}
.queue {
padding-left: 2px;
border-left: 4px solid $queueColor;
}

View File

@@ -1,17 +1,15 @@
import PropTypes from 'prop-types';
import React from 'react';
import Label from 'Components/Label';
import { kinds, sizes } from 'Helpers/Props';
import getQueueStatusText from 'Utilities/Movie/getQueueStatusText';
import firstCharToUpper from 'Utilities/String/firstCharToUpper';
import translate from 'Utilities/String/translate';
import styles from './MovieStatusLabel.css';
function getMovieStatus(hasFile, isMonitored, isAvailable, queueItem = false) {
function getMovieStatus(hasFile, isMonitored, isAvailable, queueDetails = false) {
if (queueItem) {
const queueStatus = queueItem.status;
const queueState = queueItem.trackedDownloadStatus;
if (queueDetails.items[0]) {
const queueStatus = queueDetails.items[0].status;
const queueState = queueDetails.items[0].trackedDownloadStatus;
const queueStatusText = getQueueStatusText(queueStatus, queueState);
if (queueStatusText) {
@@ -19,23 +17,19 @@ function getMovieStatus(hasFile, isMonitored, isAvailable, queueItem = false) {
}
}
if (hasFile && !isMonitored) {
return 'availNotMonitored';
}
if (hasFile) {
return 'ended';
return 'downloaded';
}
if (isAvailable && !isMonitored && !hasFile) {
return 'missingUnmonitored';
if (!isMonitored) {
return 'unmonitored';
}
if (isAvailable && !hasFile) {
return 'missingMonitored';
return 'missing';
}
return 'continuing';
return 'notAvailable';
}
function MovieStatusLabel(props) {
@@ -43,61 +37,16 @@ function MovieStatusLabel(props) {
hasMovieFiles,
monitored,
isAvailable,
queueItem,
useLabel,
colorImpairedMode
queueDetails
} = props;
let status = getMovieStatus(hasMovieFiles, monitored, isAvailable, queueItem);
const status = getMovieStatus(hasMovieFiles, monitored, isAvailable, queueDetails);
let statusClass = status;
if (status === 'availNotMonitored' || status === 'ended') {
status = 'downloaded';
}
if (status === 'missingMonitored' || status === 'missingUnmonitored') {
status = 'missing';
}
if (status === 'continuing') {
status = 'notAvailable';
}
if (queueItem) {
if (queueDetails.items.length) {
statusClass = 'queue';
}
if (useLabel) {
let kind = kinds.SUCCESS;
switch (statusClass) {
case 'queue':
kind = kinds.QUEUE;
break;
case 'missingMonitored':
kind = kinds.DANGER;
break;
case 'continuing':
kind = kinds.INFO;
break;
case 'availNotMonitored':
kind = kinds.DEFAULT;
break;
case 'missingUnmonitored':
kind = kinds.WARNING;
break;
default:
}
return (
<Label
kind={kind}
size={sizes.LARGE}
colorImpairedMode={colorImpairedMode}
>
{translate(firstCharToUpper(status))}
</Label>
);
}
return (
<span
className={styles[statusClass]}
@@ -111,9 +60,7 @@ MovieStatusLabel.propTypes = {
hasMovieFiles: PropTypes.bool.isRequired,
monitored: PropTypes.bool.isRequired,
isAvailable: PropTypes.bool.isRequired,
queueItem: PropTypes.object,
useLabel: PropTypes.bool,
colorImpairedMode: PropTypes.bool
queueDetails: PropTypes.object
};
MovieStatusLabel.defaultProps = {

View File

@@ -60,8 +60,7 @@ class MovieIndexOverviews extends Component {
columnCount: 1,
posterWidth: 162,
posterHeight: 238,
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {}),
scrollRestored: false
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {})
};
this._grid = null;
@@ -73,15 +72,13 @@ class MovieIndexOverviews extends Component {
sortKey,
overviewOptions,
jumpToCharacter,
scrollTop,
isMovieEditorActive,
isSmallScreen
} = this.props;
const {
width,
rowHeight,
scrollRestored
rowHeight
} = this.state;
if (prevProps.sortKey !== sortKey ||
@@ -100,11 +97,6 @@ class MovieIndexOverviews extends Component {
this._grid.recomputeGridSize();
}
if (this._grid && scrollTop !== 0 && !scrollRestored) {
this.setState({ scrollRestored: true });
this._grid.scrollToPosition({ scrollTop });
}
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
const index = getIndexOfFirstCharacter(items, jumpToCharacter);
@@ -270,7 +262,6 @@ MovieIndexOverviews.propTypes = {
sortKey: PropTypes.string,
overviewOptions: PropTypes.object.isRequired,
jumpToCharacter: PropTypes.string,
scrollTop: PropTypes.number.isRequired,
scroller: PropTypes.instanceOf(Element).isRequired,
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,

View File

@@ -13,7 +13,6 @@ import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
import MovieIndexProgressBar from 'Movie/Index/ProgressBar/MovieIndexProgressBar';
import MoviePoster from 'Movie/MoviePoster';
import getRelativeDate from 'Utilities/Date/getRelativeDate';
import translate from 'Utilities/String/translate';
import MovieIndexPosterInfo from './MovieIndexPosterInfo';
import styles from './MovieIndexPoster.css';
@@ -102,11 +101,6 @@ class MovieIndexPoster extends Component {
showSearchAction,
showRelativeDates,
shortDateFormat,
showReleaseDate,
showCinemaRelease,
inCinemas,
physicalRelease,
digitalRelease,
timeFormat,
isRefreshingMovie,
isSearchingMovie,
@@ -133,19 +127,6 @@ class MovieIndexPoster extends Component {
height: `${posterHeight}px`
};
let releaseDate = '';
let releaseDateType = '';
if (physicalRelease && digitalRelease) {
releaseDate = (physicalRelease < digitalRelease) ? physicalRelease : digitalRelease;
releaseDateType = (physicalRelease < digitalRelease) ? 'Released' : 'Digital';
} else if (physicalRelease && !digitalRelease) {
releaseDate = physicalRelease;
releaseDateType = 'Released';
} else if (digitalRelease && !physicalRelease) {
releaseDate = digitalRelease;
releaseDateType = 'Digital';
}
return (
<div className={styles.content}>
<div className={styles.posterContainer}>
@@ -272,67 +253,12 @@ class MovieIndexPoster extends Component {
</div>
}
{
showCinemaRelease && inCinemas &&
<div className={styles.title}>
<Icon
name={icons.IN_CINEMAS}
/> {getRelativeDate(
inCinemas,
shortDateFormat,
showRelativeDates,
{
timeFormat,
timeForToday: false
}
)}
</div>
}
{
showReleaseDate && releaseDateType === 'Released' &&
<div className={styles.title}>
<Icon
name={icons.DISC}
/> {getRelativeDate(
releaseDate,
shortDateFormat,
showRelativeDates,
{
timeFormat,
timeForToday: false
}
)}
</div>
}
{
showReleaseDate && releaseDateType === 'Digital' &&
<div className={styles.title}>
<Icon
name={icons.MOVIE_FILE}
/> {getRelativeDate(
releaseDate,
shortDateFormat,
showRelativeDates,
{
timeFormat,
timeForToday: false
}
)}
</div>
}
<MovieIndexPosterInfo
qualityProfile={qualityProfile}
showQualityProfile={showQualityProfile}
showReleaseDate={showReleaseDate}
showRelativeDates={showRelativeDates}
shortDateFormat={shortDateFormat}
timeFormat={timeFormat}
inCinemas={inCinemas}
physicalRelease={physicalRelease}
digitalRelease={digitalRelease}
{...otherProps}
/>
@@ -372,11 +298,6 @@ MovieIndexPoster.propTypes = {
showSearchAction: PropTypes.bool.isRequired,
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,
showCinemaRelease: PropTypes.bool.isRequired,
showReleaseDate: PropTypes.bool.isRequired,
inCinemas: PropTypes.string,
physicalRelease: PropTypes.string,
digitalRelease: PropTypes.string,
timeFormat: PropTypes.string.isRequired,
isRefreshingMovie: PropTypes.bool.isRequired,
isSearchingMovie: PropTypes.bool.isRequired,

View File

@@ -1,7 +1,5 @@
import PropTypes from 'prop-types';
import React from 'react';
import Icon from 'Components/Icon';
import { icons } from 'Helpers/Props';
import getRelativeDate from 'Utilities/Date/getRelativeDate';
import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
@@ -12,7 +10,6 @@ function MovieIndexPosterInfo(props) {
studio,
qualityProfile,
showQualityProfile,
showReleaseDate,
added,
inCinemas,
digitalRelease,
@@ -60,7 +57,7 @@ function MovieIndexPosterInfo(props) {
);
}
if (sortKey === 'inCinemas' && inCinemas && !showReleaseDate) {
if (sortKey === 'inCinemas' && inCinemas) {
const inCinemasDate = getRelativeDate(
inCinemas,
shortDateFormat,
@@ -73,14 +70,12 @@ function MovieIndexPosterInfo(props) {
return (
<div className={styles.info}>
<Icon
name={icons.IN_CINEMAS}
/> {inCinemasDate}
{translate('InCinemas')}: {inCinemasDate}
</div>
);
}
if (sortKey === 'digitalRelease' && digitalRelease && !showReleaseDate) {
if (sortKey === 'digitalRelease' && digitalRelease) {
const digitalReleaseDate = getRelativeDate(
digitalRelease,
shortDateFormat,
@@ -93,14 +88,12 @@ function MovieIndexPosterInfo(props) {
return (
<div className={styles.info}>
<Icon
name={icons.MOVIE_FILE}
/> {digitalReleaseDate}
{translate('Digital')}: {digitalReleaseDate}
</div>
);
}
if (sortKey === 'physicalRelease' && physicalRelease && !showReleaseDate) {
if (sortKey === 'physicalRelease' && physicalRelease) {
const physicalReleaseDate = getRelativeDate(
physicalRelease,
shortDateFormat,
@@ -113,9 +106,7 @@ function MovieIndexPosterInfo(props) {
return (
<div className={styles.info}>
<Icon
name={icons.DISC}
/> {physicalReleaseDate}
{translate('Released')}: {physicalReleaseDate}
</div>
);
}
@@ -159,7 +150,6 @@ MovieIndexPosterInfo.propTypes = {
path: PropTypes.string.isRequired,
sizeOnDisk: PropTypes.number,
sortKey: PropTypes.string.isRequired,
showReleaseDate: PropTypes.bool.isRequired,
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired

View File

@@ -38,8 +38,7 @@ function calculateRowHeight(posterHeight, sortKey, isSmallScreen, posterOptions)
detailedProgressBar,
showTitle,
showMonitored,
showQualityProfile,
showReleaseDate
showQualityProfile
} = posterOptions;
const nextAiringHeight = 19;
@@ -63,10 +62,6 @@ function calculateRowHeight(posterHeight, sortKey, isSmallScreen, posterOptions)
heights.push(19);
}
if (showReleaseDate) {
heights.push(19);
}
switch (sortKey) {
case 'studio':
case 'added':
@@ -104,8 +99,7 @@ class MovieIndexPosters extends Component {
columnCount: 1,
posterWidth: 162,
posterHeight: 238,
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {}),
scrollRestored: false
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {})
};
this._isInitialized = false;
@@ -120,16 +114,14 @@ class MovieIndexPosters extends Component {
posterOptions,
jumpToCharacter,
isSmallScreen,
isMovieEditorActive,
scrollTop
isMovieEditorActive
} = this.props;
const {
width,
columnWidth,
columnCount,
rowHeight,
scrollRestored
rowHeight
} = this.state;
if (prevProps.sortKey !== sortKey ||
@@ -148,11 +140,6 @@ class MovieIndexPosters extends Component {
this._grid.recomputeGridSize();
}
if (this._grid && scrollTop !== 0 && !scrollRestored) {
this.setState({ scrollRestored: true });
this._grid.scrollToPosition({ scrollTop });
}
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
const index = getIndexOfFirstCharacter(items, jumpToCharacter);
@@ -165,10 +152,6 @@ class MovieIndexPosters extends Component {
});
}
}
if (this._grid && scrollTop !== 0) {
this._grid.scrollToPosition({ scrollTop });
}
}
//
@@ -223,9 +206,7 @@ class MovieIndexPosters extends Component {
detailedProgressBar,
showTitle,
showMonitored,
showQualityProfile,
showCinemaRelease,
showReleaseDate
showQualityProfile
} = posterOptions;
const movieIdx = rowIndex * columnCount + columnIndex;
@@ -254,8 +235,6 @@ class MovieIndexPosters extends Component {
showTitle={showTitle}
showMonitored={showMonitored}
showQualityProfile={showQualityProfile}
showReleaseDate={showReleaseDate}
showCinemaRelease={showCinemaRelease}
showRelativeDates={showRelativeDates}
shortDateFormat={shortDateFormat}
timeFormat={timeFormat}
@@ -344,7 +323,6 @@ MovieIndexPosters.propTypes = {
sortKey: PropTypes.string,
posterOptions: PropTypes.object.isRequired,
jumpToCharacter: PropTypes.string,
scrollTop: PropTypes.number.isRequired,
scroller: PropTypes.instanceOf(Element).isRequired,
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,

View File

@@ -33,8 +33,6 @@ class MovieIndexPosterOptionsModalContent extends Component {
showTitle: props.showTitle,
showMonitored: props.showMonitored,
showQualityProfile: props.showQualityProfile,
showCinemaRelease: props.showCinemaRelease,
showReleaseDate: props.showReleaseDate,
showSearchAction: props.showSearchAction
};
}
@@ -46,8 +44,6 @@ class MovieIndexPosterOptionsModalContent extends Component {
showTitle,
showMonitored,
showQualityProfile,
showCinemaRelease,
showReleaseDate,
showSearchAction
} = this.props;
@@ -73,14 +69,6 @@ class MovieIndexPosterOptionsModalContent extends Component {
state.showQualityProfile = showQualityProfile;
}
if (showCinemaRelease !== prevProps.showCinemaRelease) {
state.showCinemaRelease = showCinemaRelease;
}
if (showReleaseDate !== prevProps.showReleaseDate) {
state.showReleaseDate = showReleaseDate;
}
if (showSearchAction !== prevProps.showSearchAction) {
state.showSearchAction = showSearchAction;
}
@@ -115,8 +103,6 @@ class MovieIndexPosterOptionsModalContent extends Component {
showTitle,
showMonitored,
showQualityProfile,
showCinemaRelease,
showReleaseDate,
showSearchAction
} = this.state;
@@ -188,30 +174,6 @@ class MovieIndexPosterOptionsModalContent extends Component {
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('ShowCinemaRelease')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showCinemaRelease"
value={showCinemaRelease}
helpText={translate('showCinemaReleaseHelpText')}
onChange={this.onChangePosterOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('ShowReleaseDate')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="showReleaseDate"
value={showReleaseDate}
helpText={translate('ShowReleaseDateHelpText')}
onChange={this.onChangePosterOption}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('ShowSearch')}</FormLabel>
@@ -244,8 +206,6 @@ MovieIndexPosterOptionsModalContent.propTypes = {
showMonitored: PropTypes.bool.isRequired,
showQualityProfile: PropTypes.bool.isRequired,
detailedProgressBar: PropTypes.bool.isRequired,
showCinemaRelease: PropTypes.bool.isRequired,
showReleaseDate: PropTypes.bool.isRequired,
showSearchAction: PropTypes.bool.isRequired,
onChangePosterOption: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired

View File

@@ -87,7 +87,6 @@ class MovieIndexTable extends Component {
isSmallScreen,
onSortPress,
scroller,
scrollTop,
allSelected,
allUnselected,
onSelectAllChange,
@@ -101,7 +100,6 @@ class MovieIndexTable extends Component {
items={items}
scrollIndex={this.state.scrollIndex}
isSmallScreen={isSmallScreen}
scrollTop={scrollTop}
scroller={scroller}
rowHeight={38}
overscanRowCount={2}
@@ -132,7 +130,6 @@ MovieIndexTable.propTypes = {
sortDirection: PropTypes.oneOf(sortDirections.all),
jumpToCharacter: PropTypes.string,
isSmallScreen: PropTypes.bool.isRequired,
scrollTop: PropTypes.number,
scroller: PropTypes.instanceOf(Element).isRequired,
onSortPress: PropTypes.func.isRequired,
allSelected: PropTypes.bool.isRequired,

View File

@@ -155,7 +155,7 @@ class FileEditModalContent extends Component {
<FormLabel>{translate('Languages')}</FormLabel>
<FormInputGroup
type={inputTypes.LANGUAGE_SELECT}
type={inputTypes.SELECT}
name="languageIds"
value={languageIds}
values={languageOptions}

View File

@@ -165,13 +165,11 @@ class MovieFileEditorRow extends Component {
<TableRowCell className={styles.actions}>
<IconButton
title={translate('EditMovieFile')}
name={icons.EDIT}
onPress={this.onFileEditPress}
/>
<IconButton
title={translate('Details')}
name={icons.MEDIA_INFO}
onPress={this.onFileDetailsPress}
/>

View File

@@ -1,6 +1,5 @@
import PropTypes from 'prop-types';
import React from 'react';
import Alert from 'Components/Alert';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
@@ -22,7 +21,6 @@ function EditImportListModalContent(props) {
advancedSettings,
isFetching,
error,
rootFolderError,
isSaving,
isTesting,
saveError,
@@ -48,8 +46,7 @@ function EditImportListModalContent(props) {
rootFolderPath,
searchOnAdd,
tags,
fields,
message
fields
} = item;
return (
@@ -65,26 +62,17 @@ function EditImportListModalContent(props) {
}
{
!isFetching && (!!error || !!rootFolderError) &&
!isFetching && !!error &&
<div>
{translate('UnableToAddANewListPleaseTryAgain')}
</div>
}
{
!isFetching && !error && !rootFolderError &&
!isFetching && !error &&
<Form
{...otherProps}
>
{
!!message &&
<Alert
className={styles.message}
kind={message.value.type}
>
{message.value.message}
</Alert>
}
<FormGroup>
<FormLabel>{translate('Name')}</FormLabel>
@@ -175,7 +163,6 @@ function EditImportListModalContent(props) {
type={inputTypes.ROOT_FOLDER_SELECT}
name="rootFolderPath"
{...rootFolderPath}
includeMissingValue={true}
onChange={onInputChange}
/>
</FormGroup>
@@ -252,7 +239,6 @@ EditImportListModalContent.propTypes = {
advancedSettings: PropTypes.bool.isRequired,
isFetching: PropTypes.bool.isRequired,
error: PropTypes.object,
rootFolderError: PropTypes.object,
isSaving: PropTypes.bool.isRequired,
isTesting: PropTypes.bool.isRequired,
saveError: PropTypes.object,

View File

@@ -9,21 +9,11 @@ import EditImportListModalContent from './EditImportListModalContent';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.advancedSettings,
(state) => state.rootFolders,
createProviderSettingsSelector('importLists'),
(advancedSettings, rootFolders, importList) => {
const {
isFetching,
isPopulated,
...otherProps
} = importList;
(advancedSettings, importList) => {
return {
advancedSettings,
isFetching: rootFolders.isFetching || isFetching,
isPopulated: rootFolders.isPopulated && isPopulated,
rootFolderError: rootFolders.error,
...otherProps
...importList
};
}
);

View File

@@ -139,7 +139,7 @@ class NamingModal extends Component {
const mediaInfoTokens = [
{ token: '{MediaInfo Simple}', example: 'x264 DTS' },
{ token: '{MediaInfo Full}', example: 'x264 DTS [EN+DE]' },
{ token: '{MediaInfo VideoCodec}', example: 'x264' },
{ token: '{MediaInfo AudioCodec}', example: 'DTS' },
{ token: '{MediaInfo AudioChannels}', example: '5.1' },
{ token: '{MediaInfo AudioLanguages}', example: '[EN+DE]' },

View File

@@ -59,17 +59,13 @@ class Notification extends Component {
onDownload,
onUpgrade,
onRename,
onMovieDelete,
onMovieFileDelete,
onMovieFileDeleteForUpgrade,
onDelete,
onHealthIssue,
supportsOnGrab,
supportsOnDownload,
supportsOnUpgrade,
supportsOnRename,
supportsOnMovieDelete,
supportsOnMovieFileDelete,
supportsOnMovieFileDeleteForUpgrade,
supportsOnDelete,
supportsOnHealthIssue
} = this.props;
@@ -84,78 +80,55 @@ class Notification extends Component {
</div>
{
supportsOnGrab && onGrab ?
supportsOnGrab && onGrab &&
<Label kind={kinds.SUCCESS}>
{translate('OnGrab')}
</Label> :
null
</Label>
}
{
supportsOnDownload && onDownload ?
supportsOnDelete && onDelete &&
<Label kind={kinds.SUCCESS}>
{translate('OnDelete')}
</Label>
}
{
supportsOnDownload && onDownload &&
<Label kind={kinds.SUCCESS}>
{translate('OnImport')}
</Label> :
null
</Label>
}
{
supportsOnUpgrade && onDownload && onUpgrade ?
supportsOnUpgrade && onDownload && onUpgrade &&
<Label kind={kinds.SUCCESS}>
{translate('OnUpgrade')}
</Label> :
null
</Label>
}
{
supportsOnRename && onRename ?
supportsOnRename && onRename &&
<Label kind={kinds.SUCCESS}>
{translate('OnRename')}
</Label> :
null
</Label>
}
{
supportsOnHealthIssue && onHealthIssue ?
supportsOnHealthIssue && onHealthIssue &&
<Label kind={kinds.SUCCESS}>
{translate('OnHealthIssue')}
</Label> :
null
</Label>
}
{
supportsOnMovieDelete && onMovieDelete ?
<Label kind={kinds.SUCCESS}>
{translate('OnMovieDelete')}
</Label> :
null
}
{
supportsOnMovieFileDelete && onMovieFileDelete ?
<Label kind={kinds.SUCCESS}>
{translate('OnMovieFileDelete')}
</Label> :
null
}
{
supportsOnMovieFileDeleteForUpgrade && onMovieFileDelete && onMovieFileDeleteForUpgrade ?
<Label kind={kinds.SUCCESS}>
{translate('OnMovieFileDeleteForUpgrade')}
</Label> :
null
}
{
!onGrab && !onDownload && !onRename && !onHealthIssue && !onMovieDelete && !onMovieFileDelete ?
!onGrab && !onDownload && !onRename && !onHealthIssue && !onDelete &&
<Label
kind={kinds.DISABLED}
outline={true}
>
{translate('Disabled')}
</Label> :
null
</Label>
}
<EditNotificationModalConnector
@@ -186,15 +159,11 @@ Notification.propTypes = {
onDownload: PropTypes.bool.isRequired,
onUpgrade: PropTypes.bool.isRequired,
onRename: PropTypes.bool.isRequired,
onMovieDelete: PropTypes.bool.isRequired,
onMovieFileDelete: PropTypes.bool.isRequired,
onMovieFileDeleteForUpgrade: PropTypes.bool.isRequired,
onDelete: PropTypes.bool.isRequired,
onHealthIssue: PropTypes.bool.isRequired,
supportsOnGrab: PropTypes.bool.isRequired,
supportsOnDownload: PropTypes.bool.isRequired,
supportsOnMovieDelete: PropTypes.bool.isRequired,
supportsOnMovieFileDelete: PropTypes.bool.isRequired,
supportsOnMovieFileDeleteForUpgrade: PropTypes.bool.isRequired,
supportsOnDelete: PropTypes.bool.isRequired,
supportsOnUpgrade: PropTypes.bool.isRequired,
supportsOnRename: PropTypes.bool.isRequired,
supportsOnHealthIssue: PropTypes.bool.isRequired,

View File

@@ -19,17 +19,13 @@ function NotificationEventItems(props) {
onDownload,
onUpgrade,
onRename,
onMovieDelete,
onMovieFileDelete,
onMovieFileDeleteForUpgrade,
onDelete,
onHealthIssue,
supportsOnGrab,
supportsOnDownload,
supportsOnUpgrade,
supportsOnRename,
supportsOnMovieDelete,
supportsOnMovieFileDelete,
supportsOnMovieFileDeleteForUpgrade,
supportsOnDelete,
supportsOnHealthIssue,
includeHealthWarnings
} = item;
@@ -93,39 +89,14 @@ function NotificationEventItems(props) {
<div>
<FormInputGroup
type={inputTypes.CHECK}
name="onMovieDelete"
helpText={translate('OnMovieDeleteHelpText')}
isDisabled={!supportsOnMovieDelete.value}
{...onMovieDelete}
name="onDelete"
helpText={translate('OnDeleteHelpText')}
isDisabled={!supportsOnDelete.value}
{...onDelete}
onChange={onInputChange}
/>
</div>
<div>
<FormInputGroup
type={inputTypes.CHECK}
name="onMovieFileDelete"
helpText={translate('OnMovieFileDeleteHelpText')}
isDisabled={!supportsOnMovieFileDelete.value}
{...onMovieFileDelete}
onChange={onInputChange}
/>
</div>
{
onMovieFileDelete.value &&
<div>
<FormInputGroup
type={inputTypes.CHECK}
name="onMovieFileDeleteForUpgrade"
helpText={translate('OnMovieFileDeleteForUpgradeHelpText')}
isDisabled={!supportsOnMovieFileDeleteForUpgrade.value}
{...onMovieFileDeleteForUpgrade}
onChange={onInputChange}
/>
</div>
}
<div>
<FormInputGroup
type={inputTypes.CHECK}

View File

@@ -1,7 +1,3 @@
.horizontalScroll {
overflow-x: auto;
}
.delayProfiles {
user-select: none;
}
@@ -29,10 +25,3 @@
width: $dragHandleWidth;
text-align: center;
}
@media only screen and (max-width: $breakpointSmall) {
.horizontalScroll {
overflow-y: hidden;
width: 100%;
}
}

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