mirror of
https://github.com/Sonarr/Sonarr.git
synced 2026-03-13 15:34:28 -04:00
Compare commits
2 Commits
phantom-di
...
phantom-jo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
243c779ca1 | ||
|
|
7ff6342503 |
24
.gitattributes
vendored
24
.gitattributes
vendored
@@ -1,12 +1,22 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
|
||||
# Explicitly set bash scripts to have unix endings
|
||||
# when checked out on windows
|
||||
*.sh text eol=lf
|
||||
distribution/debian/* text eol=lf
|
||||
macOS/Sonarr text eol=lf
|
||||
*text eol=lf
|
||||
|
||||
# Custom for Visual Studio
|
||||
*.cs diff=csharp
|
||||
*.sln merge=union
|
||||
*.csproj merge=union
|
||||
*.vbproj merge=union
|
||||
*.fsproj merge=union
|
||||
*.dbproj merge=union
|
||||
|
||||
# Standard to msysgit
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -45,10 +45,6 @@ _dotCover*
|
||||
# DevExpress CodeRush
|
||||
src/.cr/
|
||||
|
||||
# Emacs
|
||||
*~
|
||||
\#*\#
|
||||
|
||||
# NCrunch
|
||||
*.ncrunch*
|
||||
.*crunch*.local.xml
|
||||
@@ -119,9 +115,7 @@ node_modules/
|
||||
_output*
|
||||
_rawPackage/
|
||||
_dotTrace*
|
||||
_tests*
|
||||
_publish*
|
||||
_temp*
|
||||
_tests/
|
||||
*.Result.xml
|
||||
setup/Output/
|
||||
*.~is
|
||||
@@ -139,5 +133,6 @@ output/*
|
||||
.DS_Store
|
||||
|
||||
_start
|
||||
_temp_*/**/*
|
||||
|
||||
src/.idea/
|
||||
|
||||
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
[submodule "src/ExternalModules/CurlSharp"]
|
||||
path = src/ExternalModules/CurlSharp
|
||||
url = https://github.com/Sonarr/CurlSharp.git
|
||||
branch = master
|
||||
85
README.md
85
README.md
@@ -1,73 +1,58 @@
|
||||
# <img width="24px" src="./Logo/256.png" alt="Sonarr"></img> Sonarr
|
||||
# Sonarr
|
||||
|
||||
Sonarr is a PVR for Usenet and BitTorrent users. It can monitor multiple RSS feeds for new episodes of your favorite shows and will grab, sort and rename them. It can also be configured to automatically upgrade the quality of files already downloaded when a better quality format becomes available.
|
||||
|
||||
## Getting Started
|
||||
## Major Features Include:
|
||||
|
||||
- [Download](https://sonarr.tv/#download) (Linux, MacOS, Windows, Docker, etc.)
|
||||
- [Installation](https://github.com/Sonarr/Sonarr/wiki/Installation)
|
||||
- [FAQ](https://github.com/Sonarr/Sonarr/wiki/FAQ)
|
||||
- [Wiki](https://github.com/Sonarr/Sonarr/wiki)
|
||||
- [API Documentation](https://github.com/Sonarr/Sonarr/wiki/API)
|
||||
|
||||
## Support
|
||||
|
||||
- [Donate](https://sonarr.tv/donate)
|
||||
- [Discord](https://discord.gg/M6BvZn5)
|
||||
- [Reddit](https://www.reddit.com/r/sonarr)
|
||||
|
||||
## Features
|
||||
|
||||
### Current Features
|
||||
|
||||
- Support for major platforms: Windows, Linux, macOS, Raspberry Pi, etc.
|
||||
- Automatically detects new episodes
|
||||
- Can scan your existing library and download any missing episodes
|
||||
- Can watch for better quality of the episodes you already have and do an automatic upgrade. *eg. from DVD to Blu-Ray*
|
||||
- Automatic failed download handling will try another release if one fails
|
||||
- Manual search so you can pick any release or to see why a release was not downloaded automatically
|
||||
- Fully configurable episode renaming
|
||||
- Full integration with SABnzbd and NZBGet
|
||||
- Full integration with Kodi, Plex (notification, library update, metadata)
|
||||
- Full support for specials and multi-episode releases
|
||||
- And a beautiful UI
|
||||
* Support for major platforms: Windows, Linux, macOS, Raspberry Pi, etc.
|
||||
* Automatically detects new episodes
|
||||
* Can scan your existing library and download any missing episodes
|
||||
* Can watch for better quality of the episodes you already have and do an automatic upgrade. *eg. from DVD to Blu-Ray*
|
||||
* Automatic failed download handling will try another release if one fails
|
||||
* Manual search so you can pick any release or to see why a release was not downloaded automatically
|
||||
* Fully configurable episode renaming
|
||||
* Full integration with SABnzbd and NZBGet
|
||||
* Full integration with Kodi, Plex (notification, library update, metadata)
|
||||
* Full support for specials and multi-episode releases
|
||||
* And a beautiful UI
|
||||
|
||||
## Configuring Development Environment:
|
||||
|
||||
### Requirements
|
||||
|
||||
- [Visual Studio 2017](https://www.visualstudio.com/vs)
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
- [NodeJS](https://nodejs.org/en/download)
|
||||
- [Yarn](https://yarnpkg.com)
|
||||
* [Visual Studio 2017] (https://www.visualstudio.com/vs/)
|
||||
* [Git](https://git-scm.com/downloads)
|
||||
* [NodeJS](https://nodejs.org/en/download/)
|
||||
* [Yarn](https://yarnpkg.com/)
|
||||
|
||||
### Setup
|
||||
|
||||
- Make sure all the required software mentioned above are installed
|
||||
- Clone the repository recursively to get Sonarr and it's submodules
|
||||
- You can do this by running `git clone --recursive https://github.com/Sonarr/Sonarr.git`
|
||||
- Install the required Node Packages using `yarn`
|
||||
* Make sure all the required software mentioned above are installed
|
||||
* Clone the repository into your development machine. [*info*](https://help.github.com/en/articles/working-with-forks)
|
||||
* Grab the submodules `git submodule init && git submodule update`
|
||||
* Install the required Node Packages `yarn`
|
||||
|
||||
### Backend Development
|
||||
|
||||
- Run `yarn build` to build the UI
|
||||
- Open `Sonarr.sln` in Visual Studio
|
||||
- Make sure `Sonarr.Console` is set as the startup project
|
||||
- Build `Sonarr.Windows` and `Sonarr.Mono` projects
|
||||
- Build Solution
|
||||
* Run `yarn build` to build the UI
|
||||
* Open `Sonarr.sln` in Visual Studio
|
||||
* Make sure `NzbDrone.Console` is set as the startup project
|
||||
* Build `NzbDrone.Windows` and `NzbDrone.Mono` projects
|
||||
* Build Solution
|
||||
|
||||
### UI Development
|
||||
|
||||
- Run `yarn watch` to build UI and rebuild automatically when changes are detected
|
||||
- Run Sonarr.Console.exe (or debug in Visual Studio)
|
||||
* Run `yarn watch` to build UI and rebuild automatically when changes are detected
|
||||
* Run Sonarr.Console.exe (or debug in Visual Studio)
|
||||
|
||||
### Licenses
|
||||
### License
|
||||
|
||||
- [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
|
||||
- Copyright 2010-2020
|
||||
|
||||
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
|
||||
* Copyright 2010-2019
|
||||
|
||||
### Sponsors
|
||||
|
||||
- [JetBrains](http://www.jetbrains.com/) for providing us with free licenses to their great tools
|
||||
- [ReSharper](http://www.jetbrains.com/resharper/)
|
||||
- [TeamCity](http://www.jetbrains.com/teamcity/)
|
||||
* [JetBrains](http://www.jetbrains.com/) for providing us with free licenses to their great tools
|
||||
* [ReSharper](http://www.jetbrains.com/resharper/)
|
||||
* [TeamCity](http://www.jetbrains.com/teamcity/)
|
||||
|
||||
185
build.sh
185
build.sh
@@ -1,16 +1,15 @@
|
||||
#! /bin/bash
|
||||
msBuildVersion='15.0'
|
||||
outputFolder='./_output'
|
||||
outputFolderWindows='./_output_windows'
|
||||
outputFolderLinux='./_output_linux'
|
||||
outputFolderMacOS='./_output_macos'
|
||||
outputFolderMacOSApp='./_output_macos_app'
|
||||
testPackageFolder='./_tests'
|
||||
testPackageFolderWindows='./_tests_windows'
|
||||
testPackageFolderLinux='./_tests_linux'
|
||||
testPackageFolder='./_tests/'
|
||||
testSearchPattern='*.Test/bin/x86/Release'
|
||||
sourceFolder='./src'
|
||||
slnFile=$sourceFolder/Sonarr.sln
|
||||
updateSubFolder=Sonarr.Update
|
||||
updateFolder=$outputFolder/Sonarr.Update
|
||||
updateFolderMono=$outputFolderLinux/Sonarr.Update
|
||||
|
||||
nuget='tools/nuget/nuget.exe';
|
||||
vswhere='tools/vswhere/vswhere.exe';
|
||||
@@ -48,8 +47,7 @@ UpdateVersionNumber()
|
||||
verBuild=`echo "${BUILD_NUMBER}" | cut -d. -f4`
|
||||
BUILD_NUMBER=$verMajorMinorRevision.$verBuild
|
||||
echo "##teamcity[buildNumber '$BUILD_NUMBER']"
|
||||
sed -i "s/<AssemblyVersion>[0-9.*]\+<\/AssemblyVersion>/<AssemblyVersion>$BUILD_NUMBER<\/AssemblyVersion>/g" ./src/Directory.Build.props
|
||||
sed -i "s/<AssemblyConfiguration>[\$()A-Za-z-]\+<\/AssemblyConfiguration>/<AssemblyConfiguration>${BRANCH:-dev}<\/AssemblyConfiguration>/g" ./src/Directory.Build.props
|
||||
sed -i "s/^[[]assembly: Assembly\(File\|Informational\)\?Version[(]\"[0-9.*]\+\"[)]/[assembly: Assembly\1Version(\"$BUILD_NUMBER\")/g" ./src/NzbDrone*/Properties/AssemblyInfo.cs ./src/Sonarr*/Properties/AssemblyInfo.cs ./src/ServiceHelpers/*/Properties/AssemblyInfo.cs ./src/Common/CommonVersionInfo.cs
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -88,14 +86,13 @@ CleanFolder()
|
||||
|
||||
BuildWithMSBuild()
|
||||
{
|
||||
msBuildPath=`$vswhere -latest -products \* -requires Microsoft.Component.MSBuild -find MSBuild\\\\\*\*\\\\Bin\\\\MSBuild.exe`
|
||||
msBuildPath=${msBuildPath/C:\\/\/c\/}
|
||||
msBuildPath=${msBuildPath//\\/\/}
|
||||
msBuildDir=$(dirname "$msBuildPath")
|
||||
installationPath=`$vswhere -latest -products \* -requires Microsoft.Component.MSBuild -property installationPath`
|
||||
installationPath=${installationPath/C:\\/\/c\/}
|
||||
installationPath=${installationPath//\\/\/}
|
||||
msBuild="$installationPath/MSBuild/$msBuildVersion/Bin"
|
||||
echo $msBuild
|
||||
|
||||
echo $msBuildDir
|
||||
|
||||
export PATH=$msBuildDir:$PATH
|
||||
export PATH=$msBuild:$PATH
|
||||
CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x86 //t:Clean //m
|
||||
$nuget restore $slnFile
|
||||
CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x86 //t:Build //m //p:AllowedReferenceRelatedFileExtensions=.pdb
|
||||
@@ -104,15 +101,15 @@ BuildWithMSBuild()
|
||||
BuildWithXbuild()
|
||||
{
|
||||
export MONO_IOMAP=case
|
||||
CheckExitCode msbuild /t:Clean $slnFile
|
||||
CheckExitCode xbuild /t:Clean $slnFile
|
||||
mono $nuget restore $slnFile
|
||||
CheckExitCode msbuild /p:Configuration=Release /p:Platform=x86 /t:Build /p:AllowedReferenceRelatedFileExtensions=.pdb $slnFile
|
||||
CheckExitCode xbuild /p:Configuration=Release /p:Platform=x86 /t:Build /p:AllowedReferenceRelatedFileExtensions=.pdb $slnFile
|
||||
}
|
||||
|
||||
LintUI()
|
||||
{
|
||||
ProgressStart 'ESLint'
|
||||
CheckExitCode yarn lint
|
||||
CheckExitCode yarn eslint
|
||||
ProgressEnd 'ESLint'
|
||||
|
||||
ProgressStart 'Stylelint'
|
||||
@@ -125,7 +122,6 @@ Build()
|
||||
ProgressStart 'Build'
|
||||
|
||||
rm -rf $outputFolder
|
||||
rm -rf $testPackageFolder
|
||||
|
||||
if [ $runtime = "dotnet" ] ; then
|
||||
BuildWithMSBuild
|
||||
@@ -171,48 +167,6 @@ CreateMdbs()
|
||||
fi
|
||||
}
|
||||
|
||||
PatchMono()
|
||||
{
|
||||
local path=$1
|
||||
|
||||
# Below we deal with some mono incompatibilities with windows-only dotnet core/standard libs
|
||||
# See: https://github.com/mono/mono/blob/master/tools/nuget-hash-extractor/download.sh
|
||||
# That list defines assemblies that are prohibited from being loaded from the appdir, instead loading from mono GAC.
|
||||
|
||||
# We have debian dependencies to get these installed or facades from mono 5.10+
|
||||
for assembly in System.IO.Compression System.Runtime.InteropServices.RuntimeInformation System.Net.Http System.Globalization.Extensions System.Text.Encoding.CodePages System.Threading.Overlapped
|
||||
do
|
||||
if [ -e $path/$assembly.dll ]; then
|
||||
if [ -e $sourceFolder/Libraries/Mono/$assembly.dll ]; then
|
||||
echo "Copy Mono-specific facade $assembly.dll (uses win32 interop)"
|
||||
cp $sourceFolder/Libraries/Mono/$assembly.dll $path/$assembly.dll
|
||||
else
|
||||
echo "Remove $assembly.dll (uses win32 interop)"
|
||||
rm $path/$assembly.dll
|
||||
fi
|
||||
|
||||
fi
|
||||
done
|
||||
|
||||
# Copy more stable version of Vectors for mono <5.12
|
||||
if [ -e $path/System.Numerics.Vectors.dll ]; then
|
||||
packageDir="$HOME/.nuget/packages/system.numerics.vectors/4.5.0"
|
||||
|
||||
if [ ! -d "$HOME/.nuget/packages/system.numerics.vectors/4.5.0" ]; then
|
||||
# May reside in the NuGetFallback folder, which is harder to find
|
||||
# Download somewhere to get the real cache populated
|
||||
if [ $runtime = "dotnet" ] ; then
|
||||
$nuget install System.Numerics.Vectors -Version 4.5.0 -Output ./_temp/System.Numerics.Vectors
|
||||
else
|
||||
mono $nuget install System.Numerics.Vectors -Version 4.5.0 -Output ./_temp/System.Numerics.Vectors
|
||||
fi
|
||||
rm -rf ./_temp/System.Numerics.Vectors
|
||||
fi
|
||||
# Copy the netstandard2.0 version rather than net46
|
||||
cp "$packageDir/lib/netstandard2.0/System.Numerics.Vectors.dll" $path/
|
||||
fi
|
||||
}
|
||||
|
||||
PackageMono()
|
||||
{
|
||||
ProgressStart 'Creating Mono Package'
|
||||
@@ -236,14 +190,15 @@ PackageMono()
|
||||
rm -f $outputFolderLinux/sqlite3.*
|
||||
rm -f $outputFolderLinux/MediaInfo.*
|
||||
|
||||
PatchMono $outputFolderLinux
|
||||
|
||||
echo "Adding Sonarr.Core.dll.config (for dllmap)"
|
||||
cp $sourceFolder/NzbDrone.Core/Sonarr.Core.dll.config $outputFolderLinux
|
||||
|
||||
# Remove Http binding redirect by renaming it
|
||||
# We don't need this anymore once our minimum mono version is 5.10
|
||||
sed -i "s/System.Net.Http/System.Net.Http.Mono/g" $outputFolderLinux/Sonarr.Console.exe.config
|
||||
echo "Adding CurlSharp.dll.config (for dllmap)"
|
||||
cp $sourceFolder/NzbDrone.Common/CurlSharp.dll.config $outputFolderLinux
|
||||
|
||||
echo "Adding unix System.Runtime.InteropServices.RuntimeInformation.dll (for SharpRaven)"
|
||||
cp $sourceFolder/packages/System.Runtime.InteropServices.RuntimeInformation.4.3.0/runtimes/unix/lib/netstandard1.1/System.Runtime.InteropServices.RuntimeInformation.dll $outputFolderLinux
|
||||
cp $sourceFolder/packages/System.Runtime.InteropServices.RuntimeInformation.4.3.0/runtimes/unix/lib/netstandard1.1/System.Runtime.InteropServices.RuntimeInformation.dll $outputFolderLinux/Sonarr.Update
|
||||
|
||||
echo "Renaming Sonarr.Console.exe to Sonarr.exe"
|
||||
rm $outputFolderLinux/Sonarr.exe*
|
||||
@@ -255,7 +210,7 @@ PackageMono()
|
||||
rm $outputFolderLinux/Sonarr.Windows.*
|
||||
|
||||
echo "Adding Sonarr.Mono to UpdatePackage"
|
||||
cp $outputFolderLinux/Sonarr.Mono.* $outputFolderLinux/$updateSubFolder/
|
||||
cp $outputFolderLinux/Sonarr.Mono.* $updateFolderMono
|
||||
|
||||
ProgressEnd 'Creating Mono Package'
|
||||
}
|
||||
@@ -311,92 +266,54 @@ PackageMacOSApp()
|
||||
ProgressEnd 'Creating macOS App Package'
|
||||
}
|
||||
|
||||
PackageTestsMono()
|
||||
PackageTests()
|
||||
{
|
||||
ProgressStart 'Creating Mono Test Package'
|
||||
ProgressStart 'Creating Test Package'
|
||||
|
||||
rm -rf $testPackageFolderLinux
|
||||
rm -rf $testPackageFolder
|
||||
mkdir $testPackageFolder
|
||||
|
||||
echo "Copying Binaries"
|
||||
cp -r $testPackageFolder $testPackageFolderLinux
|
||||
find $sourceFolder -path $testSearchPattern -exec cp -r -u -T "{}" $testPackageFolder \;
|
||||
|
||||
if [ $runtime = "dotnet" ] ; then
|
||||
$nuget install NUnit.ConsoleRunner -Version 3.10.0 -Output $testPackageFolderLinux
|
||||
$nuget install NUnit.ConsoleRunner -Version 3.2.0 -Output $testPackageFolder
|
||||
else
|
||||
mono $nuget install NUnit.ConsoleRunner -Version 3.10.0 -Output $testPackageFolderLinux
|
||||
mono $nuget install NUnit.ConsoleRunner -Version 3.2.0 -Output $testPackageFolder
|
||||
fi
|
||||
|
||||
echo "Creating MDBs"
|
||||
CreateMdbs $testPackageFolderLinux
|
||||
cp $outputFolder/*.dll $testPackageFolder
|
||||
cp ./test.sh $testPackageFolder
|
||||
|
||||
echo "Removing PDBs"
|
||||
find $testPackageFolderLinux -name "*.pdb" -exec rm "{}" \;
|
||||
echo "Creating MDBs for tests"
|
||||
CreateMdbs $testPackageFolder
|
||||
|
||||
PatchMono $testPackageFolderLinux
|
||||
rm -f $testPackageFolder/*.log.config
|
||||
|
||||
CleanFolder $testPackageFolder true
|
||||
|
||||
echo "Adding Sonarr.Core.dll.config (for dllmap)"
|
||||
cp $sourceFolder/NzbDrone.Core/Sonarr.Core.dll.config $testPackageFolderLinux
|
||||
cp $sourceFolder/NzbDrone.Core/Sonarr.Core.dll.config $testPackageFolder
|
||||
|
||||
# Remove Http binding redirect by renaming it
|
||||
# We don't need this anymore once our minimum mono version is 5.10
|
||||
sed -i "s/System.Net.Http/System.Net.Http.Mono/g" $testPackageFolderLinux/Sonarr.Common.Test.dll.config
|
||||
echo "Adding CurlSharp.dll.config (for dllmap)"
|
||||
cp $sourceFolder/NzbDrone.Common/CurlSharp.dll.config $testPackageFolder
|
||||
|
||||
cp ./test.sh $testPackageFolderLinux/
|
||||
dos2unix $testPackageFolderLinux/test.sh
|
||||
echo "Copying CurlSharp libraries"
|
||||
cp $sourceFolder/ExternalModules/CurlSharp/libs/i386/* $testPackageFolder
|
||||
|
||||
echo "Removing Sonarr.Windows"
|
||||
rm $testPackageFolderLinux/Sonarr.Windows.*
|
||||
|
||||
rm -f $testPackageFolderLinux/*.log.config
|
||||
|
||||
CleanFolder $testPackageFolderLinux true
|
||||
|
||||
ProgressEnd 'Creating Linux Test Package'
|
||||
ProgressEnd 'Creating Test Package'
|
||||
}
|
||||
|
||||
PackageTestsWindows()
|
||||
CleanupWindowsPackage()
|
||||
{
|
||||
ProgressStart 'Creating Windows Test Package'
|
||||
|
||||
rm -rf $testPackageFolderWindows
|
||||
|
||||
echo "Copying Binaries"
|
||||
cp -r $testPackageFolder $testPackageFolderWindows
|
||||
|
||||
if [ $runtime = "dotnet" ] ; then
|
||||
$nuget install NUnit.ConsoleRunner -Version 3.10.0 -Output $testPackageFolderWindows
|
||||
else
|
||||
mono $nuget install NUnit.ConsoleRunner -Version 3.10.0 -Output $testPackageFolderWindows
|
||||
fi
|
||||
|
||||
cp ./test.sh $testPackageFolderWindows
|
||||
ProgressStart 'Cleaning Windows Package'
|
||||
|
||||
echo "Removing Sonarr.Mono"
|
||||
rm -f $testPackageFolderWindows/Sonarr.Mono.*
|
||||
|
||||
rm -f $testPackageFolderWindows/*.log.config
|
||||
|
||||
CleanFolder $testPackageFolderWindows true
|
||||
|
||||
ProgressEnd 'Creating Windows Test Package'
|
||||
}
|
||||
|
||||
PackageWindows()
|
||||
{
|
||||
ProgressStart 'Creating Windows Package'
|
||||
|
||||
rm -rf $outputFolderWindows
|
||||
|
||||
echo "Copying Binaries"
|
||||
cp -r $outputFolder $outputFolderWindows
|
||||
|
||||
echo "Removing Sonarr.Mono"
|
||||
rm -f $outputFolderWindows/Sonarr.Mono.*
|
||||
rm -f $outputFolder/Sonarr.Mono.*
|
||||
|
||||
echo "Adding Sonarr.Windows to UpdatePackage"
|
||||
cp $outputFolderWindows/Sonarr.Windows.* $outputFolderWindows/$updateSubFolder/
|
||||
cp $outputFolder/Sonarr.Windows.* $updateFolder
|
||||
|
||||
ProgressEnd 'Creating Windows Package'
|
||||
ProgressEnd 'Cleaning Windows Package'
|
||||
}
|
||||
|
||||
PublishArtifacts()
|
||||
@@ -404,11 +321,10 @@ PublishArtifacts()
|
||||
ProgressStart 'Publishing Artifacts'
|
||||
|
||||
# Tests
|
||||
echo "##teamcity[publishArtifacts '$testPackageFolderWindows/** => tests.windows.zip']"
|
||||
echo "##teamcity[publishArtifacts '$testPackageFolderLinux/** => tests.linux.zip']"
|
||||
echo "##teamcity[publishArtifacts '_tests/** => tests.zip']"
|
||||
|
||||
# Releases
|
||||
echo "##teamcity[publishArtifacts '$outputFolderWindows/** => Sonarr.$BRANCH.$BUILD_NUMBER.windows.zip!Sonarr']"
|
||||
echo "##teamcity[publishArtifacts '$outputFolder/** => Sonarr.$BRANCH.$BUILD_NUMBER.windows.zip!Sonarr']"
|
||||
echo "##teamcity[publishArtifacts '$outputFolderLinux/** => Sonarr.$BRANCH.$BUILD_NUMBER.linux.tar.gz!Sonarr']"
|
||||
echo "##teamcity[publishArtifacts '$outputFolderMacOS/** => Sonarr.$BRANCH.$BUILD_NUMBER.macos.tar.gz!Sonarr']"
|
||||
echo "##teamcity[publishArtifacts '$outputFolderMacOSApp/** => Sonarr.$BRANCH.$BUILD_NUMBER.macos.zip']"
|
||||
@@ -438,7 +354,6 @@ RunGulp
|
||||
PackageMono
|
||||
PackageMacOS
|
||||
PackageMacOSApp
|
||||
PackageTestsMono
|
||||
PackageTestsWindows
|
||||
PackageWindows
|
||||
PackageTests
|
||||
CleanupWindowsPackage
|
||||
PublishArtifacts
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# Note, this script is only used for local dev tests, this is not the script used for building the official sonarr package
|
||||
|
||||
mkdir -p /${PWD}/../_output_debian
|
||||
|
||||
docker build -f docker-build/Dockerfile -t sonarr-packager ./docker-build
|
||||
|
||||
docker run --rm -v /${PWD}/../_output_linux:/data/sonarr_bin:ro -v /${PWD}:/data/build -v /${PWD}/../_output_debian:/data/output sonarr-packager
|
||||
@@ -19,11 +19,7 @@ sed -i '/#BEGIN BUILTIN UPDATER/,/#END BUILTIN UPDATER/d' debian/preinst debian/
|
||||
echo "# Do Not Edit\nPackageVersion=$BuildVersion\nReleaseVersion=$BuildVersion\nUpdateMethod=$PackageUpdater\nBranch=$BuildBranch" > package_info
|
||||
|
||||
echo Running debuild for $BuildVersion
|
||||
if [ -z "${TEST_OUTPUT}" ]; then
|
||||
debuild -b
|
||||
else
|
||||
debuild -us -uc -b
|
||||
fi
|
||||
debuild -b
|
||||
|
||||
# Restore debian directory to the original files
|
||||
rm -rf ./debian
|
||||
@@ -36,28 +32,16 @@ sed -i '/#BEGIN BUILTIN UPDATER/d; /#END BUILTIN UPDATER/d' debian/preinst debia
|
||||
echo "# Do Not Edit\nPackageVersion=$BootstrapVersion\nReleaseVersion=$BuildVersion\nUpdateMethod=$BootstrapUpdater\nBranch=$BuildBranch" > package_info
|
||||
|
||||
echo Running debuild for $BootstrapVersion
|
||||
if [ -z "${TEST_OUTPUT}" ]; then
|
||||
debuild -b
|
||||
else
|
||||
debuild -us -uc -b
|
||||
fi
|
||||
debuild -b
|
||||
|
||||
echo Moving stuff around
|
||||
mv ../sonarr_*.deb ./
|
||||
mv ../sonarr_*.changes ./
|
||||
rm ../sonarr_*.build
|
||||
|
||||
if [ -z "${TEST_OUTPUT}" ]; then
|
||||
echo Signing Package
|
||||
dpkg-sig -k 884589CE --sign builder "sonarr_${BuildVersion}_all.deb"
|
||||
dpkg-sig -k 884589CE --sign builder "sonarr_${BootstrapVersion}_all.deb"
|
||||
echo Signing Package
|
||||
dpkg-sig -k 884589CE --sign builder "sonarr_${BuildVersion}_all.deb"
|
||||
dpkg-sig -k 884589CE --sign builder "sonarr_${BootstrapVersion}_all.deb"
|
||||
|
||||
echo running alien
|
||||
alien -r -v ./*.deb
|
||||
else
|
||||
echo "Exporting packages to ${TEST_OUTPUT}"
|
||||
dpkg -e "sonarr_${BuildVersion}_all.deb" ${TEST_OUTPUT}/sonarr-build
|
||||
dpkg -e "sonarr_${BootstrapVersion}_all.deb" ${TEST_OUTPUT}/sonarr-release
|
||||
|
||||
cp *.deb ${TEST_OUTPUT}/
|
||||
fi
|
||||
echo running alien
|
||||
alien -r -v ./*.deb
|
||||
|
||||
@@ -7,16 +7,15 @@ Vcs-Git: git@github.com:Sonarr/Sonarr.git
|
||||
Vcs-Browser: https://github.com/Sonarr/Sonarr
|
||||
Build-Depends: debhelper (>= 9),
|
||||
dh-systemd (>= 1.5),
|
||||
mono-devel (>= 5.18),
|
||||
libmono-cil-dev (>= 5.18),
|
||||
cli-common-dev (>= 0.9+xamarin5)
|
||||
mono-devel (>= 4.6),
|
||||
libmono-cil-dev (>= 4.6),
|
||||
cli-common-dev (>= 0.5.7)
|
||||
|
||||
Package: sonarr
|
||||
Architecture: all
|
||||
Provides: nzbdrone
|
||||
Conflicts: nzbdrone
|
||||
Replaces: nzbdrone
|
||||
Depends: adduser, libsqlite3-0 (>= 3.7), libmediainfo0v5 (>= 0.7.52) | libmediainfo0 (>= 0.7.52), mono-runtime (>= 5.18), ca-certificates-mono, libmono-system-net-http4.0-cil (>= 4.0.0~alpha1), ${cli:Depends}, ${misc:Depends}
|
||||
Recommends: libmediainfo0v5 (>= 18.03) | libmediainfo0 (>= 18.03)
|
||||
Suggests: sqlite3 (>= 3.7), mediainfo (>= 0.7.52)
|
||||
Depends: adduser, libsqlite3-0 (>= 3.7), libmediainfo0v5 (>= 0.7.52), mono-runtime (>= 5.4), ${cli:Depends}, ${misc:Depends}
|
||||
Recommends: sqlite3 (>= 3.7), mediainfo (>= 0.7.52), ${cli:Recommends}
|
||||
Description: Internet PVR
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
sonarr_bin/* usr/lib/sonarr/bin
|
||||
package_info usr/lib/sonarr
|
||||
sonarr_bin/* /usr/lib/sonarr/bin
|
||||
package_info /usr/lib/sonarr
|
||||
|
||||
@@ -95,7 +95,7 @@ chown -R $USER:$GROUP /usr/lib/sonarr
|
||||
sed -i "s:User=sonarr:User=$USER:g; s:Group=sonarr:Group=$GROUP:g; s:-data=/var/lib/sonarr:-data=$CONFDIR:g" /lib/systemd/system/sonarr.service
|
||||
|
||||
#BEGIN BUILTIN UPDATER
|
||||
if [ "$UPDATER" = "BuiltIn" ]; then
|
||||
if [ $1 = "upgrade" ] && [ "$UPDATER" = "BuiltIn" ]; then
|
||||
# If we upgraded, signal Sonarr to do an update check on startup instead of scheduled.
|
||||
touch $CONFDIR/update_required
|
||||
chown $USER:$GROUP $CONFDIR/update_required
|
||||
@@ -104,4 +104,4 @@ fi
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
exit 0
|
||||
exit 0
|
||||
@@ -22,25 +22,10 @@ if [ $1 = "install" ]; then
|
||||
fi
|
||||
|
||||
if [ "$psNzbDroneUnit" != "-" ] && [ -d /run/systemd/system ]; then
|
||||
if [ "$psNzbDroneUnit" = "sonarr.service" ]; then
|
||||
# Conflicts with our new sonarr.service so we have to remove it
|
||||
echo "NzbDrone systemd startup detected at $psNzbDroneUnit, stopping and removing..."
|
||||
deb-systemd-invoke stop $psNzbDroneUnit >/dev/null
|
||||
if [ -f "/etc/systemd/system/$psNzbDroneUnit" ]; then
|
||||
rm /etc/systemd/system/$psNzbDroneUnit
|
||||
fi
|
||||
if [ -f "/usr/lib/systemd/system/$psNzbDroneUnit" ]; then
|
||||
rm /usr/lib/systemd/system/$psNzbDroneUnit
|
||||
fi
|
||||
deb-systemd-helper purge $psNzbDroneUnit >/dev/null
|
||||
deb-systemd-helper unmask $psNzbDroneUnit >/dev/null
|
||||
systemctl --system daemon-reload >/dev/null || true
|
||||
else
|
||||
# Just disable it, so the user can revisit the settings later
|
||||
echo "NzbDrone systemd startup detected at $psNzbDroneUnit, stopping and disabling..."
|
||||
deb-systemd-invoke stop $psNzbDroneUnit >/dev/null
|
||||
deb-systemd-invoke mask $psNzbDroneUnit >/dev/null
|
||||
fi
|
||||
# The user used a systemd auto-startup for NzbDrone, we can deal with that.
|
||||
echo "NzbDrone systemd startup detected at $psNzbDroneUnit, stopping and disabling..."
|
||||
deb-systemd-invoke stop $psNzbDroneUnit >/dev/null
|
||||
deb-systemd-invoke mask $psNzbDroneUnit >/dev/null
|
||||
else
|
||||
# We don't support auto migration for other startup methods, so bail.
|
||||
# This leaves the sonarr package in an incomplete state.
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
#!/usr/bin/make -f
|
||||
# -*- makefile -*-
|
||||
# Sample debian/rules that uses debhelper.
|
||||
# This file was originally written by Joey Hess and Craig Small.
|
||||
# As a special exception, when this file is copied by dh-make into a
|
||||
# dh-make output file, you may use that output file without restriction.
|
||||
# This special exception was added by Craig Small in version 0.37 of dh-make.
|
||||
|
||||
# Uncomment this to turn on verbose mode.
|
||||
#export DH_VERBOSE=1
|
||||
|
||||
EXCLUDE_MODULEREFS = crypt32 httpapi __Internal ole32.dll libmonosgen-2.0 clr mscorlib mscoree.dll Microsoft.DiaSymReader.Native.x86.dll Microsoft.DiaSymReader.Native.amd64.dll
|
||||
# Note: System.Native is a dependency of System.Runtime.InteropServices.RuntimeInformation used by SharpRaven,
|
||||
# but SharpRaven doesn't use any functions that need System.Native
|
||||
EXCLUDE_MODULEREFS = crypt32 httpapi System.Native
|
||||
|
||||
%:
|
||||
dh $@ --with=systemd --with=cli
|
||||
@@ -12,7 +20,7 @@ EXCLUDE_MODULEREFS = crypt32 httpapi __Internal ole32.dll libmonosgen-2.0 clr ms
|
||||
override_dh_installinit:
|
||||
true
|
||||
|
||||
# Sonarr likes debug symbols for logging
|
||||
# Sonarr like debug symbols for logging
|
||||
override_dh_clistrip:
|
||||
|
||||
override_dh_makeclilibs:
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
ignores msbuild
|
||||
ignores libmediainfo0v5
|
||||
recommends libcurl3
|
||||
ignores msbuild
|
||||
@@ -1,23 +0,0 @@
|
||||
FROM ubuntu:xenial AS builder
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
ENV MONO_VERSION 5.18
|
||||
|
||||
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF && \
|
||||
echo "deb http://download.mono-project.com/repo/debian stable-xenial/snapshots/$MONO_VERSION main" > /etc/apt/sources.list.d/mono-official-stable.list && \
|
||||
apt-get update && apt-get install -y \
|
||||
devscripts build-essential tofrodos \
|
||||
dh-make dh-systemd \
|
||||
cli-common-dev \
|
||||
mono-complete \
|
||||
sqlite3 libcurl3 mediainfo
|
||||
|
||||
RUN apt-cache policy mono-complete
|
||||
RUN apt-cache policy cli-common-dev
|
||||
|
||||
COPY debian-start.sh /debian-start.sh
|
||||
RUN fromdos /debian-start.sh
|
||||
|
||||
WORKDIR /data
|
||||
VOLUME [ "/data/sonarr_bin", "/data/build", "/data/output" ]
|
||||
CMD /debian-start.sh
|
||||
@@ -1,18 +0,0 @@
|
||||
echo "Debian Build Dev bootstrap..."
|
||||
|
||||
export TEST_OUTPUT=/data/output
|
||||
|
||||
mkdir ${TEST_OUTPUT}
|
||||
|
||||
mkdir /data/temp
|
||||
|
||||
cp -rf /data/build/debian.sh /data/temp
|
||||
cp -rf /data/build/debian /data/temp
|
||||
cp -rf /data/sonarr_bin /data/temp/sonarr_bin
|
||||
|
||||
cd /data/temp
|
||||
|
||||
ls -al .
|
||||
|
||||
fromdos debian.sh
|
||||
sh debian.sh
|
||||
@@ -1,22 +0,0 @@
|
||||
FROM ubuntu:xenial
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
ARG MONO_VERSION=5.20
|
||||
ARG MONO_URL=stable-xenial/snapshots/$MONO_VERSION
|
||||
|
||||
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF && \
|
||||
echo "deb http://download.mono-project.com/repo/debian $MONO_URL main" > /etc/apt/sources.list.d/mono-official-stable.list && \
|
||||
apt-get update && apt-get install -y \
|
||||
tofrodos tzdata \
|
||||
mono-complete \
|
||||
sqlite3 mediainfo \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY startup.sh /startup.sh
|
||||
RUN fromdos /startup.sh
|
||||
|
||||
WORKDIR /data/
|
||||
VOLUME ["/data/_tests_linux", "/data/_output_linux", "/data/_tests_results"]
|
||||
|
||||
CMD bash /startup.sh
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
FROM ubuntu:xenial
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
ARG MONO_VERSION=5.20
|
||||
ARG MONO_URL=stable-xenial/snapshots/$MONO_VERSION
|
||||
|
||||
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF && \
|
||||
echo "deb http://download.mono-project.com/repo/debian $MONO_URL main" > /etc/apt/sources.list.d/mono-official-stable.list && \
|
||||
apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 2009837CBFFD68F45BC180471F4F90DE2A9B4BF8 && \
|
||||
echo "deb http://apt.sonarr.tv/ubuntu xenial main" > /etc/apt/sources.list.d/sonarr.list && \
|
||||
apt-get update && apt-get install -y \
|
||||
tofrodos tzdata \
|
||||
sonarr \
|
||||
sqlite3 mediainfo \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
libmono-system-runtime4.0-cil \
|
||||
libmono-system-net-http4.0-cil \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY startup.sh /startup.sh
|
||||
RUN fromdos /startup.sh
|
||||
|
||||
WORKDIR /data/
|
||||
VOLUME ["/data/_tests_linux", "/data/_output_linux", "/data/_tests_results"]
|
||||
|
||||
CMD bash /startup.sh
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
echo "Preparing Test..."
|
||||
mkdir -p /data/test
|
||||
cp -r /data/_tests_linux/* /data/test/
|
||||
cp -r /data/_output_linux /data/test/bin
|
||||
|
||||
cd /data/test
|
||||
|
||||
runTest()
|
||||
{
|
||||
bash test.sh Linux $1
|
||||
cp TestResult.xml /data/_tests_results/TestResult_$1.xml
|
||||
}
|
||||
|
||||
runTest Integration
|
||||
runTest Unit
|
||||
@@ -1,121 +0,0 @@
|
||||
|
||||
opt_parallel=
|
||||
opt_version=
|
||||
opt_mode=both
|
||||
while getopts 'pv:m:r?h' c
|
||||
do
|
||||
case $c in
|
||||
p) opt_parallel=1 ;;
|
||||
v) opt_version=$OPTARG ;;
|
||||
m) opt_mode=$OPTARG ;;
|
||||
r) opt_report=1 ;;
|
||||
?|h) printf "Usage: %s [-p] [-v mono-ver] [-m sonarr|complete]\n" $0
|
||||
printf " -p run parallel\n"
|
||||
printf " -v run specified mono version\n"
|
||||
printf " -m run only mono-'complete' or 'sonarr' package variants\n"
|
||||
printf " -r only report\n"
|
||||
exit 2
|
||||
esac
|
||||
done
|
||||
# NOTE:
|
||||
# each container has a 1gb tmpfs mounted since it greatly speeds up the normally intensive db operations
|
||||
# make sure that the docker host has enough memory to handle about ~300 MB per container, so 2-3 GB total
|
||||
# excess goes to the swap and will slow down the entire system
|
||||
|
||||
MONO_VERSIONS=""
|
||||
|
||||
# Future versions
|
||||
MONO_VERSIONS="$MONO_VERSIONS 6.10=preview-xenial"
|
||||
|
||||
# Semi-Supported versions
|
||||
MONO_VERSIONS="$MONO_VERSIONS 6.8 6.6 6.4 6.0"
|
||||
|
||||
# Supported versions
|
||||
MONO_VERSIONS="$MONO_VERSIONS 5.20 5.18"
|
||||
|
||||
# Legacy unsupported versions (but appear to work)
|
||||
MONO_VERSIONS="$MONO_VERSIONS 5.16 5.14 5.12"
|
||||
|
||||
# Legacy unsupported versions
|
||||
MONO_VERSIONS="$MONO_VERSIONS 5.10 5.8 5.4 5.0"
|
||||
#MONO_VERSIONS="$MONO_VERSIONS 4.8=stable-wheezy/snapshots/4.8"
|
||||
|
||||
if [ "$opt_version" != "" ]; then
|
||||
MONO_VERSIONS="$opt_version"
|
||||
fi
|
||||
|
||||
mkdir -p ${PWD}/../../_tests_results
|
||||
|
||||
prepOne() {
|
||||
local MONO_VERSION_PAIR=$1
|
||||
|
||||
MONO_VERSION_SPLIT=(${MONO_VERSION_PAIR//=/ })
|
||||
MONO_VERSION=${MONO_VERSION_SPLIT[0]}
|
||||
MONO_URL=${MONO_VERSION_SPLIT[1]:-"stable-xenial/snapshots/$MONO_VERSION"}
|
||||
|
||||
echo "Building Test Docker for mono $MONO_VERSION"
|
||||
|
||||
if [ "$opt_mode" != "sonarr" ]; then
|
||||
docker build -t sonarr-test-$MONO_VERSION --build-arg MONO_VERSION=$MONO_VERSION --build-arg MONO_URL=$MONO_URL --file mono/complete/Dockerfile mono
|
||||
fi
|
||||
|
||||
if [ "$opt_mode" != "complete" ] && [ "$MONO_VERSION" != "5.0" ]; then
|
||||
docker build -t sonarr-test-$MONO_VERSION-sonarr --build-arg MONO_VERSION=$MONO_VERSION --build-arg MONO_URL=$MONO_URL --file mono/sonarr/Dockerfile mono
|
||||
fi
|
||||
}
|
||||
|
||||
runOne() {
|
||||
local MONO_VERSION_PAIR=$1
|
||||
|
||||
MONO_VERSION_SPLIT=(${MONO_VERSION_PAIR//=/ })
|
||||
MONO_VERSION=${MONO_VERSION_SPLIT[0]}
|
||||
|
||||
echo "Running Test Docker for mono $MONO_VERSION"
|
||||
|
||||
if [ "$opt_mode" != "sonarr" ]; then
|
||||
dockerArgs="--rm"
|
||||
dockerArgs="$dockerArgs -v /${PWD}/../../_tests_linux:/data/_tests_linux:ro"
|
||||
dockerArgs="$dockerArgs -v /${PWD}/../../_output_linux:/data/_output_linux:ro"
|
||||
dockerArgs="$dockerArgs -v /${PWD}/../../_tests_results/mono-$MONO_VERSION:/data/_tests_results"
|
||||
dockerArgs="$dockerArgs --mount type=tmpfs,destination=//data/test,tmpfs-size=1g"
|
||||
docker run $dockerArgs sonarr-test-$MONO_VERSION
|
||||
fi
|
||||
|
||||
if [ "$opt_mode" != "complete" ] && [ "$MONO_VERSION" != "5.0" ]; then
|
||||
dockerArgs="--rm"
|
||||
dockerArgs="$dockerArgs -v /${PWD}/../../_tests_linux:/data/_tests_linux:ro"
|
||||
dockerArgs="$dockerArgs -v /${PWD}/../../_output_linux:/data/_output_linux:ro"
|
||||
dockerArgs="$dockerArgs -v /${PWD}/../../_tests_results/mono-$MONO_VERSION-sonarr:/data/_tests_results"
|
||||
dockerArgs="$dockerArgs --mount type=tmpfs,destination=//data/test,tmpfs-size=1g"
|
||||
docker run $dockerArgs sonarr-test-$MONO_VERSION-sonarr
|
||||
fi
|
||||
|
||||
echo "Finished Test Docker for mono $MONO_VERSION"
|
||||
}
|
||||
|
||||
if [ "$opt_report" != "1" ]; then
|
||||
|
||||
if [ "$opt_parallel" == "1" ]; then
|
||||
for MONO_VERSION_PAIR in $MONO_VERSIONS; do
|
||||
prepOne "$MONO_VERSION_PAIR"
|
||||
done
|
||||
fi
|
||||
|
||||
for MONO_VERSION_PAIR in $MONO_VERSIONS; do
|
||||
if [ "$opt_parallel" == "1" ]; then
|
||||
runOne "$MONO_VERSION_PAIR" &
|
||||
else
|
||||
prepOne "$MONO_VERSION_PAIR"
|
||||
runOne "$MONO_VERSION_PAIR"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$opt_parallel" == "1" ]; then
|
||||
echo "Waiting for all runs to finish"
|
||||
wait
|
||||
echo "Finished all runs"
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
grep "<test-run" ../../_tests_results/**/*.xml | sed -r 's/.*?mono-([0-9.]+(-s)?).*?_([IU]).*?\.xml.*?failed="([0-9]*)".*/\1\t\3:\tfailed \4/g' | sort -V -t.
|
||||
6
frontend/browsers.js
Normal file
6
frontend/browsers.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = [
|
||||
'>0.25%',
|
||||
'not ie 11',
|
||||
'not op_mini all',
|
||||
'not chrome < 60'
|
||||
];
|
||||
@@ -10,7 +10,8 @@ gulp.task('build',
|
||||
'webpack',
|
||||
'copyHtml',
|
||||
'copyFonts',
|
||||
'copyImages'
|
||||
'copyImages',
|
||||
'copyJs'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -5,6 +5,17 @@ const cache = require('gulp-cached');
|
||||
const livereload = require('gulp-livereload');
|
||||
const paths = require('./helpers/paths.js');
|
||||
|
||||
gulp.task('copyJs', () => {
|
||||
return gulp.src(
|
||||
[
|
||||
path.join(paths.src.root, 'polyfills.js')
|
||||
], { base: paths.src.root })
|
||||
.pipe(cache('copyJs'))
|
||||
.pipe(print())
|
||||
.pipe(gulp.dest(paths.dest.root))
|
||||
.pipe(livereload());
|
||||
});
|
||||
|
||||
gulp.task('copyHtml', () => {
|
||||
return gulp.src(paths.src.html, { base: paths.src.root })
|
||||
.pipe(cache('copyHtml'))
|
||||
|
||||
@@ -5,22 +5,15 @@ const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const errorHandler = require('./helpers/errorHandler');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
const browsers = require('../browsers');
|
||||
|
||||
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 = true;
|
||||
|
||||
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',
|
||||
@@ -30,22 +23,6 @@ const cssVarsFiles = [
|
||||
'../src/Styles/Variables/zIndexes'
|
||||
].map(require.resolve);
|
||||
|
||||
// Override the way HtmlWebpackPlugin injects the scripts
|
||||
HtmlWebpackPlugin.prototype.injectAssetsIntoHtml = function(html, assets, assetTags) {
|
||||
const head = assetTags.head.map((v) => {
|
||||
v.attributes = { rel: 'stylesheet', type: 'text/css', href: `/${v.attributes.href.replace('\\', '/')}` };
|
||||
return this.createHtmlTag(v);
|
||||
});
|
||||
const body = assetTags.body.map((v) => {
|
||||
v.attributes = { src: `/${v.attributes.src}` };
|
||||
return this.createHtmlTag(v);
|
||||
});
|
||||
|
||||
return html
|
||||
.replace('<!-- webpack bundles head -->', head.join('\r\n '))
|
||||
.replace('<!-- webpack bundles body -->', body.join('\r\n '));
|
||||
};
|
||||
|
||||
const plugins = [
|
||||
new webpack.DefinePlugin({
|
||||
__DEV__: !isProduction,
|
||||
@@ -53,12 +30,7 @@ const plugins = [
|
||||
}),
|
||||
|
||||
new MiniCssExtractPlugin({
|
||||
filename: path.join('Content', 'styles.css')
|
||||
}),
|
||||
|
||||
new HtmlWebpackPlugin({
|
||||
template: 'frontend/src/index.html',
|
||||
filename: 'index.html'
|
||||
filename: path.join('_output', uiFolder, 'Content', 'styles.css')
|
||||
})
|
||||
];
|
||||
|
||||
@@ -75,6 +47,8 @@ const config = {
|
||||
},
|
||||
|
||||
entry: {
|
||||
preload: 'preload.js',
|
||||
vendor: 'vendor.js',
|
||||
index: 'index.js'
|
||||
},
|
||||
|
||||
@@ -90,20 +64,12 @@ const config = {
|
||||
},
|
||||
|
||||
output: {
|
||||
path: distFolder,
|
||||
filename: '[name].js',
|
||||
filename: path.join('_output', uiFolder, '[name].js'),
|
||||
sourceMapFilename: '[file].map'
|
||||
},
|
||||
|
||||
optimization: {
|
||||
chunkIds: 'named',
|
||||
splitChunks: {
|
||||
chunks: 'initial'
|
||||
}
|
||||
},
|
||||
|
||||
performance: {
|
||||
hints: false
|
||||
chunkIds: 'named'
|
||||
},
|
||||
|
||||
plugins,
|
||||
@@ -117,22 +83,6 @@ const config = {
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.worker\.js$/,
|
||||
issuer: {
|
||||
// monaco-editor includes the editor.worker.js in other language workers,
|
||||
// don't use worker-loader in that case
|
||||
exclude: /monaco-editor/
|
||||
},
|
||||
use: {
|
||||
loader: 'worker-loader',
|
||||
options: {
|
||||
name: '[name].js',
|
||||
inline: inlineWebWorkers,
|
||||
fallback: !inlineWebWorkers
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.js?$/,
|
||||
exclude: /(node_modules|JsLibraries)/,
|
||||
@@ -150,7 +100,7 @@ const config = {
|
||||
loose: true,
|
||||
debug: false,
|
||||
useBuiltIns: 'entry',
|
||||
corejs: 3
|
||||
targets: browsers
|
||||
}
|
||||
]
|
||||
]
|
||||
@@ -169,9 +119,8 @@ const config = {
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
modules: {
|
||||
localIdentName: '[name]/[local]/[hash:base64:5]'
|
||||
}
|
||||
localIdentName: '[name]/[local]/[hash:base64:5]',
|
||||
modules: true
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -233,27 +182,9 @@ const config = {
|
||||
}
|
||||
};
|
||||
|
||||
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'));
|
||||
.pipe(gulp.dest('./'));
|
||||
});
|
||||
|
||||
gulp.task('webpackWatch', () => {
|
||||
@@ -261,7 +192,7 @@ gulp.task('webpackWatch', () => {
|
||||
|
||||
return webpackStream(config)
|
||||
.on('error', errorHandler)
|
||||
.pipe(gulp.dest('_output/UI'))
|
||||
.pipe(gulp.dest('./'))
|
||||
.on('error', errorHandler)
|
||||
.pipe(livereload())
|
||||
.on('error', errorHandler);
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"checkJs": false,
|
||||
"baseUrl": "src",
|
||||
"jsx": "react",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"paths": {
|
||||
"*": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
]
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
const reload = require('require-nocache')(module);
|
||||
const browsers = require('./browsers');
|
||||
|
||||
module.exports = (ctx, configPath, options) => {
|
||||
const config = {
|
||||
@@ -15,7 +16,10 @@ module.exports = (ctx, configPath, options) => {
|
||||
}, {})
|
||||
},
|
||||
'postcss-color-function': {},
|
||||
'postcss-nested': {}
|
||||
'postcss-nested': {},
|
||||
autoprefixer: {
|
||||
browsers
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import TableBody from 'Components/Table/TableBody';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import TablePager from 'Components/Table/TablePager';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
|
||||
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
@@ -56,7 +56,7 @@ class Blacklist extends Component {
|
||||
</PageToolbarSection>
|
||||
</PageToolbar>
|
||||
|
||||
<PageContentBody>
|
||||
<PageContentBodyConnector>
|
||||
{
|
||||
isFetching && !isPopulated &&
|
||||
<LoadingIndicator />
|
||||
@@ -103,7 +103,7 @@ class Blacklist extends Component {
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</PageContentBody>
|
||||
</PageContentBodyConnector>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -232,30 +232,6 @@ function HistoryDetails(props) {
|
||||
);
|
||||
}
|
||||
|
||||
if (eventType === 'downloadIgnored') {
|
||||
const {
|
||||
message
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
descriptionClassName={styles.description}
|
||||
title="Name"
|
||||
data={sourceTitle}
|
||||
/>
|
||||
|
||||
{
|
||||
!!message &&
|
||||
<DescriptionListItem
|
||||
title="Message"
|
||||
data={message}
|
||||
/>
|
||||
}
|
||||
</DescriptionList>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
|
||||
@@ -23,8 +23,6 @@ function getHeaderTitle(eventType) {
|
||||
return 'Episode File Deleted';
|
||||
case 'episodeFileRenamed':
|
||||
return 'Episode File Renamed';
|
||||
case 'downloadIgnored':
|
||||
return 'Download Ignored';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import TableBody from 'Components/Table/TableBody';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import TablePager from 'Components/Table/TablePager';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
|
||||
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
@@ -96,7 +96,7 @@ class History extends Component {
|
||||
</PageToolbarSection>
|
||||
</PageToolbar>
|
||||
|
||||
<PageContentBody>
|
||||
<PageContentBodyConnector>
|
||||
{
|
||||
isFetchingAny && !isAllPopulated &&
|
||||
<LoadingIndicator />
|
||||
@@ -147,7 +147,7 @@ class History extends Component {
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</PageContentBody>
|
||||
</PageContentBodyConnector>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,8 +19,6 @@ function getIconName(eventType) {
|
||||
return icons.DELETE;
|
||||
case 'episodeFileRenamed':
|
||||
return icons.ORGANIZE;
|
||||
case 'downloadIgnored':
|
||||
return icons.IGNORE;
|
||||
default:
|
||||
return icons.UNKNOWN;
|
||||
}
|
||||
@@ -49,8 +47,6 @@ function getTooltip(eventType, data) {
|
||||
return 'Episode file deleted';
|
||||
case 'episodeFileRenamed':
|
||||
return 'Episode file renamed';
|
||||
case 'downloadIgnored':
|
||||
return 'Episode Download Ignored';
|
||||
default:
|
||||
return 'Unknown event';
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import getRemovedItems from 'Utilities/Object/getRemovedItems';
|
||||
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
|
||||
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||
import removeOldSelectedState from 'Utilities/Table/removeOldSelectedState';
|
||||
@@ -13,7 +12,7 @@ import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import TablePager from 'Components/Table/TablePager';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
|
||||
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
@@ -37,26 +36,34 @@ class Queue extends Component {
|
||||
lastToggled: null,
|
||||
selectedState: {},
|
||||
isPendingSelected: false,
|
||||
isConfirmRemoveModalOpen: false,
|
||||
items: props.items
|
||||
isConfirmRemoveModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
items,
|
||||
isEpisodesFetching
|
||||
} = this.props;
|
||||
shouldComponentUpdate(nextProps) {
|
||||
// Don't update when fetching has completed if items have changed,
|
||||
// before episodes start fetching or when episodes start fetching.
|
||||
|
||||
if (
|
||||
(!isEpisodesFetching && prevProps.isEpisodesFetching) ||
|
||||
(hasDifferentItems(prevProps.items, items) && !items.some((e) => e.episodeId))
|
||||
this.props.isFetching &&
|
||||
nextProps.isPopulated &&
|
||||
hasDifferentItems(this.props.items, nextProps.items) &&
|
||||
nextProps.items.some((e) => e.episodeId)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.props.isEpisodesFetching && nextProps.isEpisodesFetching) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (hasDifferentItems(prevProps.items, this.props.items)) {
|
||||
this.setState((state) => {
|
||||
return {
|
||||
...removeOldSelectedState(state, getRemovedItems(prevProps.items, items)),
|
||||
items
|
||||
};
|
||||
return removeOldSelectedState(state, prevProps.items);
|
||||
});
|
||||
|
||||
return;
|
||||
@@ -64,7 +71,7 @@ class Queue extends Component {
|
||||
|
||||
const selectedIds = this.getSelectedIds();
|
||||
const isPendingSelected = _.some(this.props.items, (item) => {
|
||||
return selectedIds.indexOf(item.id) > -1 && item.status === 'delay';
|
||||
return selectedIds.indexOf(item.id) > -1 && item.status === 'Delay';
|
||||
});
|
||||
|
||||
if (isPendingSelected !== this.state.isPendingSelected) {
|
||||
@@ -100,8 +107,8 @@ class Queue extends Component {
|
||||
this.setState({ isConfirmRemoveModalOpen: true });
|
||||
}
|
||||
|
||||
onRemoveSelectedConfirmed = (payload) => {
|
||||
this.props.onRemoveSelectedPress({ ids: this.getSelectedIds(), ...payload });
|
||||
onRemoveSelectedConfirmed = (blacklist) => {
|
||||
this.props.onRemoveSelectedPress(this.getSelectedIds(), blacklist);
|
||||
this.setState({ isConfirmRemoveModalOpen: false });
|
||||
}
|
||||
|
||||
@@ -117,6 +124,7 @@ class Queue extends Component {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
items,
|
||||
isEpisodesFetching,
|
||||
isEpisodesPopulated,
|
||||
episodesError,
|
||||
@@ -124,7 +132,7 @@ class Queue extends Component {
|
||||
totalRecords,
|
||||
isGrabbing,
|
||||
isRemoving,
|
||||
isRefreshMonitoredDownloadsExecuting,
|
||||
isCheckForFinishedDownloadExecuting,
|
||||
onRefreshPress,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
@@ -134,15 +142,13 @@ class Queue extends Component {
|
||||
allUnselected,
|
||||
selectedState,
|
||||
isConfirmRemoveModalOpen,
|
||||
isPendingSelected,
|
||||
items
|
||||
isPendingSelected
|
||||
} = this.state;
|
||||
|
||||
const isRefreshing = isFetching || isEpisodesFetching || isRefreshMonitoredDownloadsExecuting;
|
||||
const isRefreshing = isFetching || isEpisodesFetching || isCheckForFinishedDownloadExecuting;
|
||||
const isAllPopulated = isPopulated && (isEpisodesPopulated || !items.length || items.every((e) => !e.episodeId));
|
||||
const hasError = error || episodesError;
|
||||
const selectedIds = this.getSelectedIds();
|
||||
const selectedCount = selectedIds.length;
|
||||
const selectedCount = this.getSelectedIds().length;
|
||||
const disableSelectedActions = selectedCount === 0;
|
||||
|
||||
return (
|
||||
@@ -191,7 +197,7 @@ class Queue extends Component {
|
||||
</PageToolbarSection>
|
||||
</PageToolbar>
|
||||
|
||||
<PageContentBody>
|
||||
<PageContentBodyConnector>
|
||||
{
|
||||
isRefreshing && !isAllPopulated &&
|
||||
<LoadingIndicator />
|
||||
@@ -248,18 +254,11 @@ class Queue extends Component {
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</PageContentBody>
|
||||
</PageContentBodyConnector>
|
||||
|
||||
<RemoveQueueItemsModal
|
||||
isOpen={isConfirmRemoveModalOpen}
|
||||
selectedCount={selectedCount}
|
||||
canIgnore={isConfirmRemoveModalOpen && (
|
||||
selectedIds.every((id) => {
|
||||
const item = items.find((i) => i.id === id);
|
||||
|
||||
return !!(item && item.seriesId && item.episodeId);
|
||||
})
|
||||
)}
|
||||
onRemovePress={this.onRemoveSelectedConfirmed}
|
||||
onModalClose={this.onConfirmRemoveModalClose}
|
||||
/>
|
||||
@@ -280,7 +279,7 @@ Queue.propTypes = {
|
||||
totalRecords: PropTypes.number,
|
||||
isGrabbing: PropTypes.bool.isRequired,
|
||||
isRemoving: PropTypes.bool.isRequired,
|
||||
isRefreshMonitoredDownloadsExecuting: PropTypes.bool.isRequired,
|
||||
isCheckForFinishedDownloadExecuting: PropTypes.bool.isRequired,
|
||||
onRefreshPress: PropTypes.func.isRequired,
|
||||
onGrabSelectedPress: PropTypes.func.isRequired,
|
||||
onRemoveSelectedPress: PropTypes.func.isRequired
|
||||
|
||||
@@ -18,13 +18,13 @@ function createMapStateToProps() {
|
||||
(state) => state.episodes,
|
||||
(state) => state.queue.options,
|
||||
(state) => state.queue.paged,
|
||||
createCommandExecutingSelector(commandNames.REFRESH_MONITORED_DOWNLOADS),
|
||||
(episodes, options, queue, isRefreshMonitoredDownloadsExecuting) => {
|
||||
createCommandExecutingSelector(commandNames.CHECK_FOR_FINISHED_DOWNLOAD),
|
||||
(episodes, options, queue, isCheckForFinishedDownloadExecuting) => {
|
||||
return {
|
||||
isEpisodesFetching: episodes.isFetching,
|
||||
isEpisodesPopulated: episodes.isPopulated,
|
||||
episodesError: episodes.error,
|
||||
isRefreshMonitoredDownloadsExecuting,
|
||||
isCheckForFinishedDownloadExecuting,
|
||||
...options,
|
||||
...queue
|
||||
};
|
||||
@@ -129,7 +129,7 @@ class QueueConnector extends Component {
|
||||
|
||||
onRefreshPress = () => {
|
||||
this.props.executeCommand({
|
||||
name: commandNames.REFRESH_MONITORED_DOWNLOADS
|
||||
name: commandNames.CHECK_FOR_FINISHED_DOWNLOAD
|
||||
});
|
||||
}
|
||||
|
||||
@@ -137,8 +137,8 @@ class QueueConnector extends Component {
|
||||
this.props.grabQueueItems({ ids });
|
||||
}
|
||||
|
||||
onRemoveSelectedPress = (payload) => {
|
||||
this.props.removeQueueItems(payload);
|
||||
onRemoveSelectedPress = (ids, blacklist) => {
|
||||
this.props.removeQueueItems({ ids, blacklist });
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -10,13 +10,13 @@ function QueueDetails(props) {
|
||||
size,
|
||||
sizeleft,
|
||||
estimatedCompletionTime,
|
||||
status,
|
||||
trackedDownloadState,
|
||||
trackedDownloadStatus,
|
||||
status: queueStatus,
|
||||
errorMessage,
|
||||
progressBar
|
||||
} = props;
|
||||
|
||||
const status = queueStatus.toLowerCase();
|
||||
|
||||
const progress = (100 - sizeleft / size * 100);
|
||||
|
||||
if (status === 'pending') {
|
||||
@@ -39,35 +39,7 @@ function QueueDetails(props) {
|
||||
);
|
||||
}
|
||||
|
||||
if (trackedDownloadStatus === 'warning') {
|
||||
return (
|
||||
<Icon
|
||||
name={icons.DOWNLOAD}
|
||||
kind={kinds.WARNING}
|
||||
title={'Downloaded - Unable to Import: check logs for details'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (trackedDownloadState === 'importPending') {
|
||||
return (
|
||||
<Icon
|
||||
name={icons.DOWNLOAD}
|
||||
kind={kinds.PURPLE}
|
||||
title={'Downloaded - Waiting to Import'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (trackedDownloadState === 'importing') {
|
||||
return (
|
||||
<Icon
|
||||
name={icons.DOWNLOAD}
|
||||
kind={kinds.PURPLE}
|
||||
title={'Downloaded - Importing'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
// TODO: show an icon when download is complete, but not imported yet?
|
||||
}
|
||||
|
||||
if (errorMessage) {
|
||||
@@ -118,8 +90,6 @@ QueueDetails.propTypes = {
|
||||
sizeleft: PropTypes.number.isRequired,
|
||||
estimatedCompletionTime: PropTypes.string,
|
||||
status: PropTypes.string.isRequired,
|
||||
trackedDownloadState: PropTypes.string.isRequired,
|
||||
trackedDownloadStatus: PropTypes.string.isRequired,
|
||||
errorMessage: PropTypes.string,
|
||||
progressBar: PropTypes.node.isRequired
|
||||
};
|
||||
|
||||
@@ -68,7 +68,6 @@ class QueueRow extends Component {
|
||||
title,
|
||||
status,
|
||||
trackedDownloadStatus,
|
||||
trackedDownloadState,
|
||||
statusMessages,
|
||||
errorMessage,
|
||||
series,
|
||||
@@ -101,8 +100,8 @@ class QueueRow extends Component {
|
||||
} = this.state;
|
||||
|
||||
const progress = 100 - (sizeleft / size * 100);
|
||||
const showInteractiveImport = status === 'completed' && trackedDownloadStatus === 'warning';
|
||||
const isPending = status === 'delay' || status === 'downloadClientUnavailable';
|
||||
const showInteractiveImport = status === 'Completed' && trackedDownloadStatus === 'Warning';
|
||||
const isPending = status === 'Delay' || status === 'DownloadClientUnavailable';
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
@@ -130,7 +129,6 @@ class QueueRow extends Component {
|
||||
sourceTitle={title}
|
||||
status={status}
|
||||
trackedDownloadStatus={trackedDownloadStatus}
|
||||
trackedDownloadState={trackedDownloadState}
|
||||
statusMessages={statusMessages}
|
||||
errorMessage={errorMessage}
|
||||
/>
|
||||
@@ -222,13 +220,9 @@ class QueueRow extends Component {
|
||||
if (name === 'quality') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
{
|
||||
quality ?
|
||||
<EpisodeQuality
|
||||
quality={quality}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
<EpisodeQuality
|
||||
quality={quality}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
@@ -356,7 +350,6 @@ class QueueRow extends Component {
|
||||
<RemoveQueueItemModal
|
||||
isOpen={isRemoveQueueItemModalOpen}
|
||||
sourceTitle={title}
|
||||
canIgnore={!!series}
|
||||
onRemovePress={this.onRemoveQueueItemModalConfirmed}
|
||||
onModalClose={this.onRemoveQueueItemModalClose}
|
||||
/>
|
||||
@@ -372,7 +365,6 @@ QueueRow.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
trackedDownloadStatus: PropTypes.string,
|
||||
trackedDownloadState: PropTypes.string,
|
||||
statusMessages: PropTypes.arrayOf(PropTypes.object),
|
||||
errorMessage: PropTypes.string,
|
||||
series: PropTypes.object,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
@@ -14,11 +15,11 @@ function createMapStateToProps() {
|
||||
createEpisodeSelector(),
|
||||
createUISettingsSelector(),
|
||||
(series, episode, uiSettings) => {
|
||||
const result = {
|
||||
showRelativeDates: uiSettings.showRelativeDates,
|
||||
shortDateFormat: uiSettings.shortDateFormat,
|
||||
timeFormat: uiSettings.timeFormat
|
||||
};
|
||||
const result = _.pick(uiSettings, [
|
||||
'showRelativeDates',
|
||||
'shortDateFormat',
|
||||
'timeFormat'
|
||||
]);
|
||||
|
||||
result.series = series;
|
||||
result.episode = episode;
|
||||
@@ -42,8 +43,8 @@ class QueueRowConnector extends Component {
|
||||
this.props.grabQueueItem({ id: this.props.id });
|
||||
}
|
||||
|
||||
onRemoveQueueItemPress = (payload) => {
|
||||
this.props.removeQueueItem({ id: this.props.id, ...payload });
|
||||
onRemoveQueueItemPress = (blacklist) => {
|
||||
this.props.removeQueueItem({ id: this.props.id, blacklist });
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -37,79 +37,63 @@ function QueueStatusCell(props) {
|
||||
const {
|
||||
sourceTitle,
|
||||
status,
|
||||
trackedDownloadStatus,
|
||||
trackedDownloadState,
|
||||
trackedDownloadStatus = 'Ok',
|
||||
statusMessages,
|
||||
errorMessage
|
||||
} = props;
|
||||
|
||||
const hasWarning = trackedDownloadStatus === 'warning';
|
||||
const hasError = trackedDownloadStatus === 'error';
|
||||
const hasWarning = trackedDownloadStatus === 'Warning';
|
||||
const hasError = trackedDownloadStatus === 'Error';
|
||||
|
||||
// status === 'downloading'
|
||||
let iconName = icons.DOWNLOADING;
|
||||
let iconKind = kinds.DEFAULT;
|
||||
let title = 'Downloading';
|
||||
|
||||
if (status === 'paused') {
|
||||
iconName = icons.PAUSED;
|
||||
title = 'Paused';
|
||||
}
|
||||
|
||||
if (status === 'queued') {
|
||||
iconName = icons.QUEUED;
|
||||
title = 'Queued';
|
||||
}
|
||||
|
||||
if (status === 'completed') {
|
||||
iconName = icons.DOWNLOADED;
|
||||
title = 'Downloaded';
|
||||
|
||||
if (trackedDownloadState === 'importPending') {
|
||||
title += ' - Waiting to Import';
|
||||
iconKind = kinds.PURPLE;
|
||||
}
|
||||
|
||||
if (trackedDownloadState === 'importing') {
|
||||
title += ' - Importing';
|
||||
iconKind = kinds.PURPLE;
|
||||
}
|
||||
|
||||
if (trackedDownloadState === 'failedPending') {
|
||||
title += ' - Waiting to Process';
|
||||
iconKind = kinds.DANGER;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasWarning) {
|
||||
iconKind = kinds.WARNING;
|
||||
}
|
||||
|
||||
if (status === 'delay') {
|
||||
if (status === 'Paused') {
|
||||
iconName = icons.PAUSED;
|
||||
title = 'Paused';
|
||||
}
|
||||
|
||||
if (status === 'Queued') {
|
||||
iconName = icons.QUEUED;
|
||||
title = 'Queued';
|
||||
}
|
||||
|
||||
if (status === 'Completed') {
|
||||
iconName = icons.DOWNLOADED;
|
||||
title = 'Downloaded';
|
||||
}
|
||||
|
||||
if (status === 'Delay') {
|
||||
iconName = icons.PENDING;
|
||||
title = 'Pending';
|
||||
}
|
||||
|
||||
if (status === 'downloadClientUnavailable') {
|
||||
if (status === 'DownloadClientUnavailable') {
|
||||
iconName = icons.PENDING;
|
||||
iconKind = kinds.WARNING;
|
||||
title = 'Pending - Download client is unavailable';
|
||||
}
|
||||
|
||||
if (status === 'failed') {
|
||||
if (status === 'Failed') {
|
||||
iconName = icons.DOWNLOADING;
|
||||
iconKind = kinds.DANGER;
|
||||
title = 'Download failed';
|
||||
}
|
||||
|
||||
if (status === 'warning') {
|
||||
if (status === 'Warning') {
|
||||
iconName = icons.DOWNLOADING;
|
||||
iconKind = kinds.WARNING;
|
||||
title = `Download warning: ${errorMessage || 'check download client for more details'}`;
|
||||
}
|
||||
|
||||
if (hasError) {
|
||||
if (status === 'completed') {
|
||||
if (status === 'Completed') {
|
||||
iconName = icons.DOWNLOAD;
|
||||
iconKind = kinds.DANGER;
|
||||
title = `Import failed: ${sourceTitle}`;
|
||||
@@ -141,15 +125,9 @@ function QueueStatusCell(props) {
|
||||
QueueStatusCell.propTypes = {
|
||||
sourceTitle: PropTypes.string.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
trackedDownloadStatus: PropTypes.string.isRequired,
|
||||
trackedDownloadState: PropTypes.string.isRequired,
|
||||
trackedDownloadStatus: PropTypes.string,
|
||||
statusMessages: PropTypes.arrayOf(PropTypes.object),
|
||||
errorMessage: PropTypes.string
|
||||
};
|
||||
|
||||
QueueStatusCell.defaultProps = {
|
||||
trackedDownloadStatus: 'Ok',
|
||||
trackedDownloadState: 'Downloading'
|
||||
};
|
||||
|
||||
export default QueueStatusCell;
|
||||
|
||||
3
frontend/src/Activity/Queue/RemoveQueueItemModal.css
Normal file
3
frontend/src/Activity/Queue/RemoveQueueItemModal.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.message {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import styles from './RemoveQueueItemModal.css';
|
||||
|
||||
class RemoveQueueItemModal extends Component {
|
||||
|
||||
@@ -20,41 +21,26 @@ class RemoveQueueItemModal extends Component {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
remove: true,
|
||||
blacklist: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
resetState = function() {
|
||||
this.setState({
|
||||
remove: true,
|
||||
blacklist: false
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onRemoveChange = ({ value }) => {
|
||||
this.setState({ remove: value });
|
||||
}
|
||||
|
||||
onBlacklistChange = ({ value }) => {
|
||||
this.setState({ blacklist: value });
|
||||
}
|
||||
|
||||
onRemoveConfirmed = () => {
|
||||
const state = this.state;
|
||||
onRemoveQueueItemConfirmed = () => {
|
||||
const blacklist = this.state.blacklist;
|
||||
|
||||
this.resetState();
|
||||
this.props.onRemovePress(state);
|
||||
this.setState({ blacklist: false });
|
||||
this.props.onRemovePress(blacklist);
|
||||
}
|
||||
|
||||
onModalClose = () => {
|
||||
this.resetState();
|
||||
this.setState({ blacklist: false });
|
||||
this.props.onModalClose();
|
||||
}
|
||||
|
||||
@@ -64,11 +50,10 @@ class RemoveQueueItemModal extends Component {
|
||||
render() {
|
||||
const {
|
||||
isOpen,
|
||||
sourceTitle,
|
||||
canIgnore
|
||||
sourceTitle
|
||||
} = this.props;
|
||||
|
||||
const { remove, blacklist } = this.state;
|
||||
const blacklist = this.state.blacklist;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -84,31 +69,17 @@ class RemoveQueueItemModal extends Component {
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<div>
|
||||
<div className={styles.message}>
|
||||
Are you sure you want to remove '{sourceTitle}' from the queue?
|
||||
</div>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Remove From Download Client</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="remove"
|
||||
value={remove}
|
||||
helpTextWarning="Removing will remove the download and the file(s) from the download client."
|
||||
isDisabled={!canIgnore}
|
||||
onChange={this.onRemoveChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Blacklist Release</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="blacklist"
|
||||
value={blacklist}
|
||||
helpText="Starts a search for this episode again and prevents this release from being grabbed again"
|
||||
helpText="Prevents Sonarr from automatically grabbing this episode again"
|
||||
onChange={this.onBlacklistChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
@@ -122,7 +93,7 @@ class RemoveQueueItemModal extends Component {
|
||||
|
||||
<Button
|
||||
kind={kinds.DANGER}
|
||||
onPress={this.onRemoveConfirmed}
|
||||
onPress={this.onRemoveQueueItemConfirmed}
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
@@ -136,7 +107,6 @@ class RemoveQueueItemModal extends Component {
|
||||
RemoveQueueItemModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
sourceTitle: PropTypes.string.isRequired,
|
||||
canIgnore: PropTypes.bool.isRequired,
|
||||
onRemovePress: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -21,41 +21,26 @@ class RemoveQueueItemsModal extends Component {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
remove: true,
|
||||
blacklist: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
resetState = function() {
|
||||
this.setState({
|
||||
remove: true,
|
||||
blacklist: false
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onRemoveChange = ({ value }) => {
|
||||
this.setState({ remove: value });
|
||||
}
|
||||
// Listeners
|
||||
|
||||
onBlacklistChange = ({ value }) => {
|
||||
this.setState({ blacklist: value });
|
||||
}
|
||||
|
||||
onRemoveConfirmed = () => {
|
||||
const state = this.state;
|
||||
onRemoveQueueItemConfirmed = () => {
|
||||
const blacklist = this.state.blacklist;
|
||||
|
||||
this.resetState();
|
||||
this.props.onRemovePress(state);
|
||||
this.setState({ blacklist: false });
|
||||
this.props.onRemovePress(blacklist);
|
||||
}
|
||||
|
||||
onModalClose = () => {
|
||||
this.resetState();
|
||||
this.setState({ blacklist: false });
|
||||
this.props.onModalClose();
|
||||
}
|
||||
|
||||
@@ -65,11 +50,10 @@ class RemoveQueueItemsModal extends Component {
|
||||
render() {
|
||||
const {
|
||||
isOpen,
|
||||
selectedCount,
|
||||
canIgnore
|
||||
selectedCount
|
||||
} = this.props;
|
||||
|
||||
const { remove, blacklist } = this.state;
|
||||
const blacklist = this.state.blacklist;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -90,23 +74,7 @@ class RemoveQueueItemsModal extends Component {
|
||||
</div>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Remove From Download Client</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="remove"
|
||||
value={remove}
|
||||
helpTextWarning="Removing will remove the download and the file(s) from the download client."
|
||||
isDisabled={!canIgnore}
|
||||
onChange={this.onRemoveChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>
|
||||
Blacklist Release{selectedCount > 1 ? 's' : ''}
|
||||
</FormLabel>
|
||||
|
||||
<FormLabel>Blacklist Release</FormLabel>
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="blacklist"
|
||||
@@ -125,7 +93,7 @@ class RemoveQueueItemsModal extends Component {
|
||||
|
||||
<Button
|
||||
kind={kinds.DANGER}
|
||||
onPress={this.onRemoveConfirmed}
|
||||
onPress={this.onRemoveQueueItemConfirmed}
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
@@ -139,7 +107,6 @@ class RemoveQueueItemsModal extends Component {
|
||||
RemoveQueueItemsModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
selectedCount: PropTypes.number.isRequired,
|
||||
canIgnore: PropTypes.bool.isRequired,
|
||||
onRemovePress: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ function TimeleftCell(props) {
|
||||
timeFormat
|
||||
} = props;
|
||||
|
||||
if (status === 'delay') {
|
||||
if (status === 'Delay') {
|
||||
const date = getRelativeDate(estimatedCompletionTime, shortDateFormat, showRelativeDates);
|
||||
const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true });
|
||||
|
||||
@@ -33,7 +33,7 @@ function TimeleftCell(props) {
|
||||
);
|
||||
}
|
||||
|
||||
if (status === 'downloadClientUnavailable') {
|
||||
if (status === 'DownloadClientUnavailable') {
|
||||
const date = getRelativeDate(estimatedCompletionTime, shortDateFormat, showRelativeDates);
|
||||
const time = formatTime(estimatedCompletionTime, timeFormat, { includeMinuteZero: true });
|
||||
|
||||
@@ -47,7 +47,7 @@ function TimeleftCell(props) {
|
||||
);
|
||||
}
|
||||
|
||||
if (!timeleft || status === 'completed' || status === 'failed') {
|
||||
if (!timeleft) {
|
||||
return (
|
||||
<TableRowCell className={styles.timeleft}>
|
||||
-
|
||||
|
||||
@@ -35,20 +35,14 @@
|
||||
.message {
|
||||
margin-top: 30px;
|
||||
text-align: center;
|
||||
font-weight: 300;
|
||||
font-size: $largeFontSize;
|
||||
}
|
||||
|
||||
.helpText {
|
||||
margin-bottom: 10px;
|
||||
font-weight: 300;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.noSeriesText {
|
||||
margin-top: 80px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.noResults {
|
||||
margin-bottom: 10px;
|
||||
font-weight: 300;
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import Button from 'Components/Link/Button';
|
||||
import Link from 'Components/Link/Link';
|
||||
import Icon from 'Components/Icon';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import TextInput from 'Components/Form/TextInput';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
|
||||
import AddNewSeriesSearchResultConnector from './AddNewSeriesSearchResultConnector';
|
||||
import styles from './AddNewSeries.css';
|
||||
|
||||
@@ -79,8 +78,7 @@ class AddNewSeries extends Component {
|
||||
render() {
|
||||
const {
|
||||
error,
|
||||
items,
|
||||
hasExistingSeries
|
||||
items
|
||||
} = this.props;
|
||||
|
||||
const term = this.state.term;
|
||||
@@ -88,7 +86,7 @@ class AddNewSeries extends Component {
|
||||
|
||||
return (
|
||||
<PageContent title="Add New Series">
|
||||
<PageContentBody>
|
||||
<PageContentBodyConnector>
|
||||
<div className={styles.searchContainer}>
|
||||
<div className={styles.searchIconContainer}>
|
||||
<Icon
|
||||
@@ -123,13 +121,8 @@ class AddNewSeries extends Component {
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && !!error ?
|
||||
<div className={styles.message}>
|
||||
<div className={styles.helpText}>
|
||||
Failed to load search results, please try again.
|
||||
</div>
|
||||
<div>{getErrorMessage(error)}</div>
|
||||
</div> : null
|
||||
!isFetching && !!error &&
|
||||
<div>Failed to load search results, please try again.</div>
|
||||
}
|
||||
|
||||
{
|
||||
@@ -162,36 +155,15 @@ class AddNewSeries extends Component {
|
||||
}
|
||||
|
||||
{
|
||||
term ?
|
||||
null :
|
||||
!term &&
|
||||
<div className={styles.message}>
|
||||
<div className={styles.helpText}>
|
||||
It's easy to add a new series, just start typing the name the series you want to add.
|
||||
</div>
|
||||
<div className={styles.helpText}>It's easy to add a new series, just start typing the name the series you want to add.</div>
|
||||
<div>You can also search using TVDB ID of a show. eg. tvdb:71663</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
!term && !hasExistingSeries ?
|
||||
<div className={styles.message}>
|
||||
<div className={styles.noSeriesText}>
|
||||
You haven't added any series yet, do you want to import some or all of your series first?
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
to="/add/import"
|
||||
kind={kinds.PRIMARY}
|
||||
>
|
||||
Import Existing Series
|
||||
</Button>
|
||||
</div>
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
|
||||
<div />
|
||||
</PageContentBody>
|
||||
</PageContentBodyConnector>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
@@ -204,7 +176,6 @@ AddNewSeries.propTypes = {
|
||||
isAdding: PropTypes.bool.isRequired,
|
||||
addError: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
hasExistingSeries: PropTypes.bool.isRequired,
|
||||
onSeriesLookupChange: PropTypes.func.isRequired,
|
||||
onClearSeriesLookup: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -10,15 +10,13 @@ import AddNewSeries from './AddNewSeries';
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.addSeries,
|
||||
(state) => state.series.items.length,
|
||||
(state) => state.router.location,
|
||||
(addSeries, existingSeriesCount, location) => {
|
||||
(addSeries, location) => {
|
||||
const { params } = parseUrl(location.search);
|
||||
|
||||
return {
|
||||
...addSeries,
|
||||
term: params.term,
|
||||
hasExistingSeries: existingSeriesCount > 0
|
||||
...addSeries
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -14,7 +14,6 @@ import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import Popover from 'Components/Tooltip/Popover';
|
||||
import SeriesPoster from 'Series/SeriesPoster';
|
||||
import * as seriesTypes from 'Utilities/Series/seriesTypes';
|
||||
import SeriesMonitoringOptionsPopoverContent from 'AddSeries/SeriesMonitoringOptionsPopoverContent';
|
||||
import SeriesTypePopoverContent from 'AddSeries/SeriesTypePopoverContent';
|
||||
import styles from './AddNewSeriesModalContent.css';
|
||||
@@ -28,19 +27,10 @@ class AddNewSeriesModalContent extends Component {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
seriesType: props.initialSeriesType === seriesTypes.STANDARD ?
|
||||
props.seriesType.value :
|
||||
props.initialSeriesType,
|
||||
searchForMissingEpisodes: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.seriesType.value !== prevProps.seriesType.value) {
|
||||
this.setState({ seriesType: this.props.seriesType.value });
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
@@ -57,12 +47,7 @@ class AddNewSeriesModalContent extends Component {
|
||||
}
|
||||
|
||||
onAddSeriesPress = () => {
|
||||
const {
|
||||
searchForMissingEpisodes,
|
||||
seriesType
|
||||
} = this.state;
|
||||
|
||||
this.props.onAddSeriesPress(searchForMissingEpisodes, seriesType);
|
||||
this.props.onAddSeriesPress(this.state.searchForMissingEpisodes);
|
||||
}
|
||||
|
||||
//
|
||||
@@ -81,11 +66,9 @@ class AddNewSeriesModalContent extends Component {
|
||||
languageProfileId,
|
||||
seriesType,
|
||||
seasonFolder,
|
||||
folder,
|
||||
tags,
|
||||
showLanguageProfile,
|
||||
isSmallScreen,
|
||||
isWindows,
|
||||
onModalClose,
|
||||
onInputChange,
|
||||
...otherProps
|
||||
@@ -132,15 +115,6 @@ class AddNewSeriesModalContent extends Component {
|
||||
<FormInputGroup
|
||||
type={inputTypes.ROOT_FOLDER_SELECT}
|
||||
name="rootFolderPath"
|
||||
valueOptions={{
|
||||
seriesFolder: folder,
|
||||
isWindows
|
||||
}}
|
||||
selectedValueOptions={{
|
||||
seriesFolder: folder,
|
||||
isWindows
|
||||
}}
|
||||
helpText={`'${folder}' subfolder will be created automatically`}
|
||||
onChange={onInputChange}
|
||||
{...rootFolderPath}
|
||||
/>
|
||||
@@ -215,7 +189,6 @@ class AddNewSeriesModalContent extends Component {
|
||||
name="seriesType"
|
||||
onChange={onInputChange}
|
||||
{...seriesType}
|
||||
value={this.state.seriesType}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
@@ -278,7 +251,6 @@ AddNewSeriesModalContent.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
year: PropTypes.number.isRequired,
|
||||
overview: PropTypes.string,
|
||||
initialSeriesType: PropTypes.string.isRequired,
|
||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
isAdding: PropTypes.bool.isRequired,
|
||||
addError: PropTypes.object,
|
||||
@@ -288,11 +260,9 @@ AddNewSeriesModalContent.propTypes = {
|
||||
languageProfileId: PropTypes.object,
|
||||
seriesType: PropTypes.object.isRequired,
|
||||
seasonFolder: PropTypes.object.isRequired,
|
||||
folder: PropTypes.string.isRequired,
|
||||
tags: PropTypes.object.isRequired,
|
||||
showLanguageProfile: PropTypes.bool.isRequired,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
isWindows: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired,
|
||||
onInputChange: PropTypes.func.isRequired,
|
||||
onAddSeriesPress: PropTypes.func.isRequired
|
||||
|
||||
@@ -4,7 +4,6 @@ import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { setAddSeriesDefault, addSeries } from 'Store/Actions/addSeriesActions';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
|
||||
import selectSettings from 'Store/Selectors/selectSettings';
|
||||
import AddNewSeriesModalContent from './AddNewSeriesModalContent';
|
||||
|
||||
@@ -13,8 +12,7 @@ function createMapStateToProps() {
|
||||
(state) => state.addSeries,
|
||||
(state) => state.settings.languageProfiles,
|
||||
createDimensionsSelector(),
|
||||
createSystemStatusSelector(),
|
||||
(addSeriesState, languageProfiles, dimensions, systemStatus) => {
|
||||
(addSeriesState, languageProfiles, dimensions) => {
|
||||
const {
|
||||
isAdding,
|
||||
addError,
|
||||
@@ -34,7 +32,6 @@ function createMapStateToProps() {
|
||||
isSmallScreen: dimensions.isSmallScreen,
|
||||
validationErrors,
|
||||
validationWarnings,
|
||||
isWindows: systemStatus.isWindows,
|
||||
...settings
|
||||
};
|
||||
}
|
||||
@@ -55,13 +52,14 @@ class AddNewSeriesModalContentConnector extends Component {
|
||||
this.props.setAddSeriesDefault({ [name]: value });
|
||||
}
|
||||
|
||||
onAddSeriesPress = (searchForMissingEpisodes, seriesType) => {
|
||||
onAddSeriesPress = (searchForMissingEpisodes) => {
|
||||
const {
|
||||
tvdbId,
|
||||
rootFolderPath,
|
||||
monitor,
|
||||
qualityProfileId,
|
||||
languageProfileId,
|
||||
seriesType,
|
||||
seasonFolder,
|
||||
tags
|
||||
} = this.props;
|
||||
@@ -72,7 +70,7 @@ class AddNewSeriesModalContentConnector extends Component {
|
||||
monitor: monitor.value,
|
||||
qualityProfileId: qualityProfileId.value,
|
||||
languageProfileId: languageProfileId.value,
|
||||
seriesType,
|
||||
seriesType: seriesType.value,
|
||||
seasonFolder: seasonFolder.value,
|
||||
tags: tags.value,
|
||||
searchForMissingEpisodes
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
.searchResult {
|
||||
position: relative;
|
||||
display: flex;
|
||||
margin: 20px 0;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.underlay {
|
||||
@add-mixin cover;
|
||||
|
||||
background-color: $white;
|
||||
color: inherit;
|
||||
transition: background 500ms;
|
||||
|
||||
&:hover {
|
||||
@@ -19,25 +14,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
.overlay {
|
||||
@add-mixin linkOverlay;
|
||||
|
||||
position: relative;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.poster {
|
||||
flex: 0 0 170px;
|
||||
margin-right: 20px;
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 0 1 100%;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
font-weight: 300;
|
||||
font-size: 36px;
|
||||
}
|
||||
@@ -47,22 +30,9 @@
|
||||
color: $disabledColor;
|
||||
}
|
||||
|
||||
.tvdbLink {
|
||||
composes: link from '~Components/Link/Link.css';
|
||||
|
||||
margin-top: -4px;
|
||||
margin-left: auto;
|
||||
color: $textColor;
|
||||
}
|
||||
|
||||
.tvdbLinkIcon {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.alreadyExistsIcon {
|
||||
margin-left: 10px;
|
||||
color: #37bc9b;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.overview {
|
||||
|
||||
@@ -24,7 +24,7 @@ class AddNewSeriesSearchResult extends Component {
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (!prevProps.isExistingSeries && this.props.isExistingSeries) {
|
||||
this.onAddSeriesModalClose();
|
||||
this.onAddSerisModalClose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,14 +35,10 @@ class AddNewSeriesSearchResult extends Component {
|
||||
this.setState({ isNewAddSeriesModalOpen: true });
|
||||
}
|
||||
|
||||
onAddSeriesModalClose = () => {
|
||||
onAddSerisModalClose = () => {
|
||||
this.setState({ isNewAddSeriesModalOpen: false });
|
||||
}
|
||||
|
||||
onTVDBLinkPress = (event) => {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
@@ -57,8 +53,6 @@ class AddNewSeriesSearchResult extends Component {
|
||||
overview,
|
||||
statistics,
|
||||
ratings,
|
||||
folder,
|
||||
seriesType,
|
||||
images,
|
||||
isExistingSeries,
|
||||
isSmallScreen
|
||||
@@ -78,13 +72,11 @@ class AddNewSeriesSearchResult extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.searchResult}>
|
||||
<div>
|
||||
<Link
|
||||
className={styles.underlay}
|
||||
className={styles.searchResult}
|
||||
{...linkProps}
|
||||
/>
|
||||
|
||||
<div className={styles.overlay}>
|
||||
>
|
||||
{
|
||||
isSmallScreen ?
|
||||
null :
|
||||
@@ -96,7 +88,7 @@ class AddNewSeriesSearchResult extends Component {
|
||||
/>
|
||||
}
|
||||
|
||||
<div className={styles.content}>
|
||||
<div>
|
||||
<div className={styles.title}>
|
||||
{title}
|
||||
|
||||
@@ -118,18 +110,6 @@ class AddNewSeriesSearchResult extends Component {
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
<Link
|
||||
className={styles.tvdbLink}
|
||||
to={`http://www.thetvdb.com/?tab=series&id=${tvdbId}`}
|
||||
onPress={this.onTVDBLinkPress}
|
||||
>
|
||||
<Icon
|
||||
className={styles.tvdbLinkIcon}
|
||||
name={icons.EXTERNAL_LINK}
|
||||
size={28}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -166,24 +146,13 @@ class AddNewSeriesSearchResult extends Component {
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
status === 'upcoming' ?
|
||||
<Label
|
||||
kind={kinds.INFO}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
Upcoming
|
||||
</Label> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className={styles.overview}>
|
||||
{overview}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<AddNewSeriesModal
|
||||
isOpen={isNewAddSeriesModalOpen && !isExistingSeries}
|
||||
@@ -191,10 +160,8 @@ class AddNewSeriesSearchResult extends Component {
|
||||
title={title}
|
||||
year={year}
|
||||
overview={overview}
|
||||
folder={folder}
|
||||
initialSeriesType={seriesType}
|
||||
images={images}
|
||||
onModalClose={this.onAddSeriesModalClose}
|
||||
onModalClose={this.onAddSerisModalClose}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -211,8 +178,6 @@ AddNewSeriesSearchResult.propTypes = {
|
||||
overview: PropTypes.string,
|
||||
statistics: PropTypes.object.isRequired,
|
||||
ratings: PropTypes.object.isRequired,
|
||||
folder: PropTypes.string.isRequired,
|
||||
seriesType: PropTypes.string.isRequired,
|
||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
isExistingSeries: PropTypes.bool.isRequired,
|
||||
isSmallScreen: PropTypes.bool.isRequired
|
||||
|
||||
@@ -5,7 +5,7 @@ import selectAll from 'Utilities/Table/selectAll';
|
||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
|
||||
import ImportSeriesTableConnector from './ImportSeriesTableConnector';
|
||||
import ImportSeriesFooterConnector from './ImportSeriesFooterConnector';
|
||||
|
||||
@@ -21,15 +21,17 @@ class ImportSeries extends Component {
|
||||
allSelected: false,
|
||||
allUnselected: false,
|
||||
lastToggled: null,
|
||||
selectedState: {}
|
||||
selectedState: {},
|
||||
contentBody: null,
|
||||
scrollTop: 0
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
setScrollerRef = (ref) => {
|
||||
this.setState({ scroller: ref });
|
||||
setContentBodyRef = (ref) => {
|
||||
this.setState({ contentBody: ref });
|
||||
}
|
||||
|
||||
//
|
||||
@@ -92,13 +94,13 @@ class ImportSeries extends Component {
|
||||
allSelected,
|
||||
allUnselected,
|
||||
selectedState,
|
||||
scroller
|
||||
contentBody
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<PageContent title="Import Series">
|
||||
<PageContentBody
|
||||
registerScroller={this.setScrollerRef}
|
||||
<PageContentBodyConnector
|
||||
ref={this.setContentBodyRef}
|
||||
onScroll={this.onScroll}
|
||||
>
|
||||
{
|
||||
@@ -119,21 +121,23 @@ class ImportSeries extends Component {
|
||||
}
|
||||
|
||||
{
|
||||
!rootFoldersError && rootFoldersPopulated && !!unmappedFolders.length && scroller &&
|
||||
!rootFoldersError && rootFoldersPopulated && !!unmappedFolders.length && contentBody &&
|
||||
<ImportSeriesTableConnector
|
||||
rootFolderId={rootFolderId}
|
||||
unmappedFolders={unmappedFolders}
|
||||
allSelected={allSelected}
|
||||
allUnselected={allUnselected}
|
||||
selectedState={selectedState}
|
||||
scroller={scroller}
|
||||
contentBody={contentBody}
|
||||
showLanguageProfile={showLanguageProfile}
|
||||
scrollTop={this.state.scrollTop}
|
||||
onSelectAllChange={this.onSelectAllChange}
|
||||
onSelectedChange={this.onSelectedChange}
|
||||
onRemoveSelectedStateItem={this.onRemoveSelectedStateItem}
|
||||
onScroll={this.onScroll}
|
||||
/>
|
||||
}
|
||||
</PageContentBody>
|
||||
</PageContentBodyConnector>
|
||||
|
||||
{
|
||||
!rootFoldersError && rootFoldersPopulated && !!unmappedFolders.length &&
|
||||
|
||||
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import VirtualTableRow from 'Components/Table/VirtualTableRow';
|
||||
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
|
||||
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
|
||||
import ImportSeriesSelectSeriesConnector from './SelectSeries/ImportSeriesSelectSeriesConnector';
|
||||
@@ -9,6 +10,7 @@ import styles from './ImportSeriesRow.css';
|
||||
|
||||
function ImportSeriesRow(props) {
|
||||
const {
|
||||
style,
|
||||
id,
|
||||
monitor,
|
||||
qualityProfileId,
|
||||
@@ -24,7 +26,7 @@ function ImportSeriesRow(props) {
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<VirtualTableRow style={style}>
|
||||
<VirtualTableSelectCell
|
||||
inputClassName={styles.selectInput}
|
||||
id={id}
|
||||
@@ -88,14 +90,14 @@ function ImportSeriesRow(props) {
|
||||
<ImportSeriesSelectSeriesConnector
|
||||
id={id}
|
||||
isExistingSeries={isExistingSeries}
|
||||
onInputChange={onInputChange}
|
||||
/>
|
||||
</VirtualTableRowCell>
|
||||
</>
|
||||
</VirtualTableRow>
|
||||
);
|
||||
}
|
||||
|
||||
ImportSeriesRow.propTypes = {
|
||||
style: PropTypes.object.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
monitor: PropTypes.string.isRequired,
|
||||
qualityProfileId: PropTypes.number.isRequired,
|
||||
|
||||
@@ -2,7 +2,6 @@ import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import VirtualTable from 'Components/Table/VirtualTable';
|
||||
import VirtualTableRow from 'Components/Table/VirtualTableRow';
|
||||
import ImportSeriesHeader from './ImportSeriesHeader';
|
||||
import ImportSeriesRowConnector from './ImportSeriesRowConnector';
|
||||
|
||||
@@ -113,19 +112,15 @@ class ImportSeriesTable extends Component {
|
||||
const item = items[rowIndex];
|
||||
|
||||
return (
|
||||
<VirtualTableRow
|
||||
<ImportSeriesRowConnector
|
||||
key={key}
|
||||
style={style}
|
||||
>
|
||||
<ImportSeriesRowConnector
|
||||
key={item.id}
|
||||
rootFolderId={rootFolderId}
|
||||
showLanguageProfile={showLanguageProfile}
|
||||
isSelected={selectedState[item.id]}
|
||||
onSelectedChange={onSelectedChange}
|
||||
id={item.id}
|
||||
/>
|
||||
</VirtualTableRow>
|
||||
rootFolderId={rootFolderId}
|
||||
showLanguageProfile={showLanguageProfile}
|
||||
isSelected={selectedState[item.id]}
|
||||
onSelectedChange={onSelectedChange}
|
||||
id={item.id}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -138,10 +133,12 @@ class ImportSeriesTable extends Component {
|
||||
allSelected,
|
||||
allUnselected,
|
||||
isSmallScreen,
|
||||
scroller,
|
||||
contentBody,
|
||||
showLanguageProfile,
|
||||
scrollTop,
|
||||
selectedState,
|
||||
onSelectAllChange
|
||||
onSelectAllChange,
|
||||
onScroll
|
||||
} = this.props;
|
||||
|
||||
if (!items.length) {
|
||||
@@ -151,9 +148,10 @@ class ImportSeriesTable extends Component {
|
||||
return (
|
||||
<VirtualTable
|
||||
items={items}
|
||||
scroller={scroller}
|
||||
contentBody={contentBody}
|
||||
isSmallScreen={isSmallScreen}
|
||||
rowHeight={52}
|
||||
scrollTop={scrollTop}
|
||||
overscanRowCount={2}
|
||||
rowRenderer={this.rowRenderer}
|
||||
header={
|
||||
@@ -165,6 +163,7 @@ class ImportSeriesTable extends Component {
|
||||
/>
|
||||
}
|
||||
selectedState={selectedState}
|
||||
onScroll={onScroll}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -184,13 +183,15 @@ ImportSeriesTable.propTypes = {
|
||||
selectedState: PropTypes.object.isRequired,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
allSeries: PropTypes.arrayOf(PropTypes.object),
|
||||
scroller: PropTypes.instanceOf(Element).isRequired,
|
||||
contentBody: PropTypes.object.isRequired,
|
||||
showLanguageProfile: PropTypes.bool.isRequired,
|
||||
scrollTop: PropTypes.number.isRequired,
|
||||
onSelectAllChange: PropTypes.func.isRequired,
|
||||
onSelectedChange: PropTypes.func.isRequired,
|
||||
onRemoveSelectedStateItem: PropTypes.func.isRequired,
|
||||
onSeriesLookup: PropTypes.func.isRequired,
|
||||
onSetImportSeriesValue: PropTypes.func.isRequired
|
||||
onSetImportSeriesValue: PropTypes.func.isRequired,
|
||||
onScroll: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default ImportSeriesTable;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { queueLookupSeries, setImportSeriesValue } from 'Store/Actions/importSeriesActions';
|
||||
import createImportSeriesItemSelector from 'Store/Selectors/createImportSeriesItemSelector';
|
||||
import * as seriesTypes from 'Utilities/Series/seriesTypes';
|
||||
import ImportSeriesSelectSeries from './ImportSeriesSelectSeries';
|
||||
|
||||
function createMapStateToProps() {
|
||||
@@ -41,23 +41,13 @@ class ImportSeriesSelectSeriesConnector extends Component {
|
||||
onSeriesSelect = (tvdbId) => {
|
||||
const {
|
||||
id,
|
||||
items,
|
||||
onInputChange
|
||||
items
|
||||
} = this.props;
|
||||
|
||||
const selectedSeries = items.find((item) => item.tvdbId === tvdbId);
|
||||
|
||||
this.props.setImportSeriesValue({
|
||||
id,
|
||||
selectedSeries
|
||||
selectedSeries: _.find(items, { tvdbId })
|
||||
});
|
||||
|
||||
if (selectedSeries.seriesType !== seriesTypes.STANDARD) {
|
||||
onInputChange({
|
||||
name: 'seriesType',
|
||||
value: selectedSeries.seriesType
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@@ -79,7 +69,6 @@ ImportSeriesSelectSeriesConnector.propTypes = {
|
||||
items: PropTypes.arrayOf(PropTypes.object),
|
||||
selectedSeries: PropTypes.object,
|
||||
isSelected: PropTypes.bool,
|
||||
onInputChange: PropTypes.func.isRequired,
|
||||
queueLookupSeries: PropTypes.func.isRequired,
|
||||
setImportSeriesValue: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ import FieldSet from 'Components/FieldSet';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import FileBrowserModal from 'Components/FileBrowser/FileBrowserModal';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
|
||||
import RootFolders from 'RootFolder/RootFolders';
|
||||
import styles from './ImportSeriesSelectFolder.css';
|
||||
|
||||
@@ -53,7 +53,7 @@ class ImportSeriesSelectFolder extends Component {
|
||||
|
||||
return (
|
||||
<PageContent title="Import Series">
|
||||
<PageContentBody>
|
||||
<PageContentBodyConnector>
|
||||
{
|
||||
isFetching && !isPopulated &&
|
||||
<LoadingIndicator />
|
||||
@@ -75,10 +75,10 @@ class ImportSeriesSelectFolder extends Component {
|
||||
Some tips to ensure the import goes smoothly:
|
||||
<ul>
|
||||
<li className={styles.tip}>
|
||||
Make sure that your files include the quality in their filenames. eg. <span className={styles.code}>episode.s02e15.bluray.mkv</span>
|
||||
Make sure your files include the quality in the name. eg. <span className={styles.code}>episode.s02e15.bluray.mkv</span>
|
||||
</li>
|
||||
<li className={styles.tip}>
|
||||
Point Sonarr to the folder containing all of your tv shows, not a specific one. eg. <span className={styles.code}>"{isWindows ? 'C:\\tv shows' : '/tv shows'}"</span> and not <span className={styles.code}>"{isWindows ? 'C:\\tv shows\\the simpsons' : '/tv shows/the simpsons'}"</span>
|
||||
Point Sonarr to the folder containing all of your tv shows not a specific one. eg. <span className={styles.code}>"{isWindows ? 'C:\\tv shows' : '/tv shows'}"</span> and not <span className={styles.code}>"{isWindows ? 'C:\\tv shows\\the simpsons' : '/tv shows/the simpsons'}"</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -132,7 +132,7 @@ class ImportSeriesSelectFolder extends Component {
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</PageContentBody>
|
||||
</PageContentBodyConnector>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ import BackupsConnector from 'System/Backup/BackupsConnector';
|
||||
import UpdatesConnector from 'System/Updates/UpdatesConnector';
|
||||
import LogsTableConnector from 'System/Events/LogsTableConnector';
|
||||
import Logs from 'System/Logs/Logs';
|
||||
import Diagnostic from 'Diagnostic/Diagnostic';
|
||||
|
||||
function AppRoutes(props) {
|
||||
const {
|
||||
@@ -230,15 +229,6 @@ function AppRoutes(props) {
|
||||
component={Logs}
|
||||
/>
|
||||
|
||||
{/*
|
||||
Diagnostics
|
||||
*/}
|
||||
|
||||
<Route
|
||||
path="/diag"
|
||||
component={Diagnostic}
|
||||
/>
|
||||
|
||||
{/*
|
||||
Not Found
|
||||
*/}
|
||||
|
||||
@@ -3,10 +3,9 @@ import React, { Component } from 'react';
|
||||
import { align, icons } from 'Helpers/Props';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import Measure from 'Components/Measure';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
|
||||
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||
import NoSeries from 'Series/NoSeries';
|
||||
@@ -77,10 +76,8 @@ class CalendarPage extends Component {
|
||||
filters,
|
||||
hasSeries,
|
||||
missingEpisodeIds,
|
||||
isRssSyncExecuting,
|
||||
isSearchingForMissing,
|
||||
useCurrentPage,
|
||||
onRssSyncPress,
|
||||
onFilterSelect
|
||||
} = this.props;
|
||||
|
||||
@@ -102,15 +99,6 @@ class CalendarPage extends Component {
|
||||
onPress={this.onGetCalendarLinkPress}
|
||||
/>
|
||||
|
||||
<PageToolbarSeparator />
|
||||
|
||||
<PageToolbarButton
|
||||
label="RSS Sync"
|
||||
iconName={icons.RSS}
|
||||
isSpinning={isRssSyncExecuting}
|
||||
onPress={onRssSyncPress}
|
||||
/>
|
||||
|
||||
<PageToolbarButton
|
||||
label="Search for Missing"
|
||||
iconName={icons.SEARCH}
|
||||
@@ -138,7 +126,7 @@ class CalendarPage extends Component {
|
||||
</PageToolbarSection>
|
||||
</PageToolbar>
|
||||
|
||||
<PageContentBody
|
||||
<PageContentBodyConnector
|
||||
className={styles.calendarPageBody}
|
||||
innerClassName={styles.calendarInnerPageBody}
|
||||
>
|
||||
@@ -159,7 +147,7 @@ class CalendarPage extends Component {
|
||||
hasSeries &&
|
||||
<LegendConnector />
|
||||
}
|
||||
</PageContentBody>
|
||||
</PageContentBodyConnector>
|
||||
|
||||
<CalendarLinkModal
|
||||
isOpen={isCalendarLinkModalOpen}
|
||||
@@ -180,12 +168,10 @@ CalendarPage.propTypes = {
|
||||
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
hasSeries: PropTypes.bool.isRequired,
|
||||
missingEpisodeIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
isRssSyncExecuting: PropTypes.bool.isRequired,
|
||||
isSearchingForMissing: PropTypes.bool.isRequired,
|
||||
useCurrentPage: PropTypes.bool.isRequired,
|
||||
onSearchMissingPress: PropTypes.func.isRequired,
|
||||
onDaysCountChange: PropTypes.func.isRequired,
|
||||
onRssSyncPress: PropTypes.func.isRequired,
|
||||
onFilterSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -3,14 +3,11 @@ import { createSelector } from 'reselect';
|
||||
import moment from 'moment';
|
||||
import { isCommandExecuting } from 'Utilities/Command';
|
||||
import isBefore from 'Utilities/Date/isBefore';
|
||||
import * as commandNames from 'Commands/commandNames';
|
||||
import withCurrentPage from 'Components/withCurrentPage';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import { searchMissing, setCalendarDaysCount, setCalendarFilter } from 'Store/Actions/calendarActions';
|
||||
import createSeriesCountSelector from 'Store/Selectors/createSeriesCountSelector';
|
||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
|
||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||
import CalendarPage from './CalendarPage';
|
||||
|
||||
function createMissingEpisodeIdsSelector() {
|
||||
@@ -62,7 +59,6 @@ function createMapStateToProps() {
|
||||
createSeriesCountSelector(),
|
||||
createUISettingsSelector(),
|
||||
createMissingEpisodeIdsSelector(),
|
||||
createCommandExecutingSelector(commandNames.RSS_SYNC),
|
||||
createIsSearchingSelector(),
|
||||
(
|
||||
selectedFilterKey,
|
||||
@@ -70,7 +66,6 @@ function createMapStateToProps() {
|
||||
seriesCount,
|
||||
uiSettings,
|
||||
missingEpisodeIds,
|
||||
isRssSyncExecuting,
|
||||
isSearchingForMissing
|
||||
) => {
|
||||
return {
|
||||
@@ -79,7 +74,6 @@ function createMapStateToProps() {
|
||||
colorImpairedMode: uiSettings.enableColorImpairedMode,
|
||||
hasSeries: !!seriesCount,
|
||||
missingEpisodeIds,
|
||||
isRssSyncExecuting,
|
||||
isSearchingForMissing
|
||||
};
|
||||
}
|
||||
@@ -88,12 +82,6 @@ function createMapStateToProps() {
|
||||
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
onRssSyncPress() {
|
||||
dispatch(executeCommand({
|
||||
name: commandNames.RSS_SYNC
|
||||
}));
|
||||
},
|
||||
|
||||
onSearchMissingPress(episodeIds) {
|
||||
dispatch(searchMissing({ episodeIds }));
|
||||
},
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
flex: 1 0 14.28%;
|
||||
overflow: hidden;
|
||||
min-height: 70px;
|
||||
border-bottom: 1px solid $calendarBorderColor;
|
||||
border-left: 1px solid $calendarBorderColor;
|
||||
border-bottom: 1px solid $borderColor;
|
||||
border-left: 1px solid $borderColor;
|
||||
}
|
||||
|
||||
.isSingleDay {
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
.dayOfMonth {
|
||||
padding-right: 5px;
|
||||
border-bottom: 1px solid $calendarBorderColor;
|
||||
border-bottom: 1px solid $borderColor;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.days {
|
||||
display: flex;
|
||||
border-right: 1px solid $calendarBorderColor;
|
||||
border-right: 1px solid $borderColor;
|
||||
}
|
||||
|
||||
.day,
|
||||
|
||||
@@ -16,13 +16,10 @@
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.episodeInfo {
|
||||
color: $calendarTextDim;
|
||||
}
|
||||
|
||||
.seriesTitle,
|
||||
.episodeTitle {
|
||||
@add-mixin truncate;
|
||||
|
||||
flex: 1 0 1px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
@@ -40,10 +37,6 @@
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.airTime {
|
||||
color: $calendarTextDim;
|
||||
}
|
||||
|
||||
/*
|
||||
* Status
|
||||
*/
|
||||
|
||||
@@ -200,7 +200,7 @@ class CalendarEvent extends Component {
|
||||
</div>
|
||||
}
|
||||
|
||||
<div className={styles.airTime}>
|
||||
<div>
|
||||
{formatTime(airDateUtc, timeFormat)} - {formatTime(endTime.toISOString(), timeFormat, { includeMinuteZero: true })}
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
.seriesTitle {
|
||||
@add-mixin truncate;
|
||||
|
||||
flex: 1 0 1px;
|
||||
margin-right: 10px;
|
||||
color: #3a3f51;
|
||||
@@ -22,12 +23,10 @@
|
||||
|
||||
.airTime {
|
||||
flex: 1 0 1px;
|
||||
color: $calendarTextDim;
|
||||
}
|
||||
|
||||
.episodeInfo {
|
||||
margin-left: 10px;
|
||||
color: $calendarTextDim;
|
||||
}
|
||||
|
||||
.absoluteEpisodeNumber {
|
||||
@@ -81,7 +80,3 @@
|
||||
.premiere {
|
||||
composes: premiere from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
.unaired {
|
||||
composes: unaired from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
@@ -11,12 +11,10 @@ function CalendarEventQueueDetails(props) {
|
||||
sizeleft,
|
||||
estimatedCompletionTime,
|
||||
status,
|
||||
trackedDownloadState,
|
||||
trackedDownloadStatus,
|
||||
errorMessage
|
||||
} = props;
|
||||
|
||||
const progress = size ? (100 - sizeleft / size * 100) : 0;
|
||||
const progress = (100 - sizeleft / size * 100);
|
||||
|
||||
return (
|
||||
<QueueDetails
|
||||
@@ -25,8 +23,6 @@ function CalendarEventQueueDetails(props) {
|
||||
sizeleft={sizeleft}
|
||||
estimatedCompletionTime={estimatedCompletionTime}
|
||||
status={status}
|
||||
trackedDownloadState={trackedDownloadState}
|
||||
trackedDownloadStatus={trackedDownloadStatus}
|
||||
errorMessage={errorMessage}
|
||||
progressBar={
|
||||
<div title={`Episode is downloading - ${progress.toFixed(1)}% ${title}`}>
|
||||
@@ -48,8 +44,6 @@ CalendarEventQueueDetails.propTypes = {
|
||||
sizeleft: PropTypes.number.isRequired,
|
||||
estimatedCompletionTime: PropTypes.string,
|
||||
status: PropTypes.string.isRequired,
|
||||
trackedDownloadState: PropTypes.string.isRequired,
|
||||
trackedDownloadStatus: PropTypes.string.isRequired,
|
||||
errorMessage: PropTypes.string
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export const APPLICATION_UPDATE = 'ApplicationUpdate';
|
||||
export const BACKUP = 'Backup';
|
||||
export const REFRESH_MONITORED_DOWNLOADS = 'RefreshMonitoredDownloads';
|
||||
export const CHECK_FOR_FINISHED_DOWNLOAD = 'CheckForFinishedDownload';
|
||||
export const CLEAR_BLACKLIST = 'ClearBlacklist';
|
||||
export const CLEAR_LOGS = 'ClearLog';
|
||||
export const CUTOFF_UNMET_EPISODE_SEARCH = 'CutoffUnmetEpisodeSearch';
|
||||
|
||||
@@ -56,7 +56,7 @@ class ErrorBoundary extends Component {
|
||||
|
||||
ErrorBoundary.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
errorComponent: PropTypes.elementType.isRequired
|
||||
errorComponent: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default ErrorBoundary;
|
||||
|
||||
@@ -144,7 +144,6 @@ class FileBrowserModalContent extends Component {
|
||||
<Scroller
|
||||
ref={this.setScrollerRef}
|
||||
className={styles.scroller}
|
||||
scrollDirection={scrollDirections.BOTH}
|
||||
>
|
||||
{
|
||||
!!error &&
|
||||
@@ -153,10 +152,7 @@ class FileBrowserModalContent extends Component {
|
||||
|
||||
{
|
||||
isPopulated && !error &&
|
||||
<Table
|
||||
horizontalScroll={false}
|
||||
columns={columns}
|
||||
>
|
||||
<Table columns={columns}>
|
||||
<TableBody>
|
||||
{
|
||||
emptyParent &&
|
||||
|
||||
@@ -132,7 +132,6 @@ class FilterBuilderModalContent extends Component {
|
||||
filterBuilderProps,
|
||||
isSaving,
|
||||
saveError,
|
||||
onCancelPress,
|
||||
onModalClose
|
||||
} = this.props;
|
||||
|
||||
@@ -172,7 +171,7 @@ class FilterBuilderModalContent extends Component {
|
||||
filters.map((filter, index) => {
|
||||
return (
|
||||
<FilterBuilderRow
|
||||
key={`${filter.key}-${index}`}
|
||||
key={index}
|
||||
index={index}
|
||||
sectionItems={sectionItems}
|
||||
filterBuilderProps={filterBuilderProps}
|
||||
@@ -191,7 +190,7 @@ class FilterBuilderModalContent extends Component {
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={onCancelPress}>
|
||||
<Button onPress={onModalClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
@@ -221,7 +220,6 @@ FilterBuilderModalContent.propTypes = {
|
||||
dispatchDeleteCustomFilter: PropTypes.func.isRequired,
|
||||
onSaveCustomFilterPress: PropTypes.func.isRequired,
|
||||
dispatchSetFilter: PropTypes.func.isRequired,
|
||||
onCancelPress: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ class FilterBuilderRowValue extends Component {
|
||||
tagList={tagList}
|
||||
allowNew={!tagList.length}
|
||||
kind={kinds.DEFAULT}
|
||||
delimiters={['Tab', 'Enter']}
|
||||
delimiters={[9, 13]}
|
||||
maxSuggestionsLength={100}
|
||||
minQueryLength={0}
|
||||
tagComponent={FilterBuilderRowValueTag}
|
||||
|
||||
@@ -3,21 +3,16 @@ import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import sortByName from 'Utilities/Array/sortByName';
|
||||
import { filterBuilderTypes } from 'Helpers/Props';
|
||||
import * as filterTypes from 'Helpers/Props/filterTypes';
|
||||
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
||||
|
||||
function createTagListSelector() {
|
||||
return createSelector(
|
||||
(state, { filterType }) => filterType,
|
||||
(state, { sectionItems }) => sectionItems,
|
||||
(state, { selectedFilterBuilderProp }) => selectedFilterBuilderProp,
|
||||
(filterType, sectionItems, selectedFilterBuilderProp) => {
|
||||
(sectionItems, selectedFilterBuilderProp) => {
|
||||
if (
|
||||
(selectedFilterBuilderProp.type === filterBuilderTypes.NUMBER ||
|
||||
selectedFilterBuilderProp.type === filterBuilderTypes.STRING) &&
|
||||
filterType !== filterTypes.EQUAL &&
|
||||
filterType !== filterBuilderTypes.NOT_EQUAL ||
|
||||
!selectedFilterBuilderProp.optionsSelector
|
||||
selectedFilterBuilderProp.type === filterBuilderTypes.NUMBER ||
|
||||
selectedFilterBuilderProp.type === filterBuilderTypes.STRING
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
.tag {
|
||||
height: 21px;
|
||||
|
||||
&.isLastTag {
|
||||
.or {
|
||||
display: none;
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import React from 'react';
|
||||
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
||||
|
||||
const seriesStatusList = [
|
||||
const protocols = [
|
||||
{ id: 'continuing', name: 'Continuing' },
|
||||
{ id: 'upcoming', name: 'Upcoming' },
|
||||
{ id: 'ended', name: 'Ended' }
|
||||
];
|
||||
|
||||
function SeriesStatusFilterBuilderRowValue(props) {
|
||||
return (
|
||||
<FilterBuilderRowValue
|
||||
tagList={seriesStatusList}
|
||||
tagList={protocols}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -29,10 +29,10 @@ function CustomFiltersModalContent(props) {
|
||||
|
||||
<ModalBody>
|
||||
{
|
||||
customFilters.map((customFilter) => {
|
||||
customFilters.map((customFilter, index) => {
|
||||
return (
|
||||
<CustomFilter
|
||||
key={customFilter.id}
|
||||
key={index}
|
||||
id={customFilter.id}
|
||||
label={customFilter.label}
|
||||
filters={customFilter.filters}
|
||||
|
||||
@@ -34,17 +34,6 @@ class FilterModal extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
onCancelPress = () => {
|
||||
if (this.state.filterBuilder) {
|
||||
this.setState({
|
||||
filterBuilder: false,
|
||||
id: null
|
||||
});
|
||||
} else {
|
||||
this.onModalClose();
|
||||
}
|
||||
}
|
||||
|
||||
onModalClose = () => {
|
||||
this.setState({
|
||||
filterBuilder: false,
|
||||
@@ -78,7 +67,6 @@ class FilterModal extends Component {
|
||||
<FilterBuilderModalContentConnector
|
||||
{...otherProps}
|
||||
id={id}
|
||||
onCancelPress={this.onCancelPress}
|
||||
onModalClose={this.onModalClose}
|
||||
/> :
|
||||
<CustomFiltersModalContentConnector
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
.suggestionsList {
|
||||
margin: 5px 0;
|
||||
padding-left: 0;
|
||||
max-height: 200px;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -176,7 +176,7 @@ class AutoSuggestInput extends Component {
|
||||
className: classNames(
|
||||
className,
|
||||
hasError && styles.hasError,
|
||||
hasWarning && styles.hasWarning
|
||||
hasWarning && styles.hasWarning,
|
||||
),
|
||||
name,
|
||||
value,
|
||||
@@ -234,7 +234,7 @@ AutoSuggestInput.propTypes = {
|
||||
minHeight: PropTypes.number.isRequired,
|
||||
maxHeight: PropTypes.number.isRequired,
|
||||
getSuggestionValue: PropTypes.func.isRequired,
|
||||
renderInputComponent: PropTypes.elementType,
|
||||
renderInputComponent: PropTypes.func,
|
||||
renderSuggestion: PropTypes.func.isRequired,
|
||||
onInputChange: PropTypes.func,
|
||||
onInputKeyDown: PropTypes.func,
|
||||
|
||||
@@ -2,13 +2,13 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { fetchOptions, clearOptions } from 'Store/Actions/providerOptionActions';
|
||||
import { fetchDevices, clearDevices } from 'Store/Actions/deviceActions';
|
||||
import DeviceInput from './DeviceInput';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state, { value }) => value,
|
||||
(state) => state.providerOptions,
|
||||
(state) => state.devices,
|
||||
(value, devices) => {
|
||||
|
||||
return {
|
||||
@@ -37,8 +37,8 @@ function createMapStateToProps() {
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
dispatchFetchOptions: fetchOptions,
|
||||
dispatchClearOptions: clearOptions
|
||||
dispatchFetchDevices: fetchDevices,
|
||||
dispatchClearDevices: clearDevices
|
||||
};
|
||||
|
||||
class DeviceInputConnector extends Component {
|
||||
@@ -51,7 +51,7 @@ class DeviceInputConnector extends Component {
|
||||
}
|
||||
|
||||
componentWillUnmount = () => {
|
||||
this.props.dispatchClearOptions();
|
||||
// this.props.dispatchClearDevices();
|
||||
}
|
||||
|
||||
//
|
||||
@@ -61,14 +61,10 @@ class DeviceInputConnector extends Component {
|
||||
const {
|
||||
provider,
|
||||
providerData,
|
||||
dispatchFetchOptions
|
||||
dispatchFetchDevices
|
||||
} = this.props;
|
||||
|
||||
dispatchFetchOptions({
|
||||
action: 'getDevices',
|
||||
provider,
|
||||
providerData
|
||||
});
|
||||
dispatchFetchDevices({ provider, providerData });
|
||||
}
|
||||
|
||||
//
|
||||
@@ -96,8 +92,8 @@ DeviceInputConnector.propTypes = {
|
||||
providerData: PropTypes.object.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
dispatchFetchOptions: PropTypes.func.isRequired,
|
||||
dispatchClearOptions: PropTypes.func.isRequired
|
||||
dispatchFetchDevices: PropTypes.func.isRequired,
|
||||
dispatchClearDevices: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(DeviceInputConnector);
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
.enhancedSelect {
|
||||
composes: input from '~Components/Form/Input.css';
|
||||
composes: link from '~Components/Link/Link.css';
|
||||
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 6px 16px;
|
||||
width: 100%;
|
||||
height: 35px;
|
||||
border: 1px solid $inputBorderColor;
|
||||
border-radius: 4px;
|
||||
background-color: $white;
|
||||
box-shadow: inset 0 1px 1px $inputBoxShadowColor;
|
||||
color: $black;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.hasError {
|
||||
@@ -45,7 +56,6 @@
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
max-width: 90%;
|
||||
max-height: 100%;
|
||||
width: 350px !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import React, { Component } from 'react';
|
||||
import { Manager, Popper, Reference } from 'react-popper';
|
||||
import classNames from 'classnames';
|
||||
import getUniqueElememtId from 'Utilities/getUniqueElementId';
|
||||
import { isMobile as isMobileUtil } from 'Utilities/mobile';
|
||||
import isMobileUtil from 'Utilities/isMobile';
|
||||
import * as keyCodes from 'Utilities/Constants/keyCodes';
|
||||
import { icons, sizes, scrollDirections } from 'Helpers/Props';
|
||||
import Icon from 'Components/Icon';
|
||||
@@ -262,7 +262,6 @@ class EnhancedSelectInput extends Component {
|
||||
isDisabled,
|
||||
hasError,
|
||||
hasWarning,
|
||||
valueOptions,
|
||||
selectedValueOptions,
|
||||
selectedValueComponent: SelectedValueComponent,
|
||||
optionComponent: OptionComponent
|
||||
@@ -364,7 +363,6 @@ class EnhancedSelectInput extends Component {
|
||||
key={v.key}
|
||||
id={v.key}
|
||||
isSelected={index === selectedIndex}
|
||||
{...valueOptions}
|
||||
{...v}
|
||||
isMobile={false}
|
||||
onSelect={this.onSelect}
|
||||
@@ -406,7 +404,6 @@ class EnhancedSelectInput extends Component {
|
||||
key={v.key}
|
||||
id={v.key}
|
||||
isSelected={index === selectedIndex}
|
||||
{...valueOptions}
|
||||
{...v}
|
||||
isMobile={true}
|
||||
onSelect={this.onSelect}
|
||||
@@ -434,10 +431,9 @@ EnhancedSelectInput.propTypes = {
|
||||
isDisabled: PropTypes.bool,
|
||||
hasError: PropTypes.bool,
|
||||
hasWarning: PropTypes.bool,
|
||||
valueOptions: PropTypes.object.isRequired,
|
||||
selectedValueOptions: PropTypes.object.isRequired,
|
||||
selectedValueComponent: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
|
||||
optionComponent: PropTypes.elementType,
|
||||
optionComponent: PropTypes.func,
|
||||
onChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -445,7 +441,6 @@ EnhancedSelectInput.defaultProps = {
|
||||
className: styles.enhancedSelect,
|
||||
disabledClassName: styles.isDisabled,
|
||||
isDisabled: false,
|
||||
valueOptions: {},
|
||||
selectedValueOptions: {},
|
||||
selectedValueComponent: HintedSelectInputSelectedValue,
|
||||
optionComponent: HintedSelectInputOption
|
||||
|
||||
@@ -63,7 +63,7 @@ class EnhancedSelectInputOption extends Component {
|
||||
|
||||
EnhancedSelectInputOption.propTypes = {
|
||||
className: PropTypes.string.isRequired,
|
||||
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
isSelected: PropTypes.bool.isRequired,
|
||||
isDisabled: PropTypes.bool.isRequired,
|
||||
isHidden: PropTypes.bool.isRequired,
|
||||
|
||||
@@ -14,7 +14,6 @@ import PasswordInput from './PasswordInput';
|
||||
import PathInputConnector from './PathInputConnector';
|
||||
import QualityProfileSelectInputConnector from './QualityProfileSelectInputConnector';
|
||||
import LanguageProfileSelectInputConnector from './LanguageProfileSelectInputConnector';
|
||||
import IndexerSelectInputConnector from './IndexerSelectInputConnector';
|
||||
import RootFolderSelectInputConnector from './RootFolderSelectInputConnector';
|
||||
import SeriesTypeSelectInput from './SeriesTypeSelectInput';
|
||||
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||
@@ -62,9 +61,6 @@ function getComponent(type) {
|
||||
case inputTypes.LANGUAGE_PROFILE_SELECT:
|
||||
return LanguageProfileSelectInputConnector;
|
||||
|
||||
case inputTypes.INDEXER_SELECT:
|
||||
return IndexerSelectInputConnector;
|
||||
|
||||
case inputTypes.ROOT_FOLDER_SELECT:
|
||||
return RootFolderSelectInputConnector;
|
||||
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import sortByName from 'Utilities/Array/sortByName';
|
||||
import { fetchIndexers } from 'Store/Actions/settingsActions';
|
||||
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.settings.indexers,
|
||||
(state, { includeAny }) => includeAny,
|
||||
(indexers, includeAny) => {
|
||||
const {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
items
|
||||
} = indexers;
|
||||
|
||||
const values = _.map(items.sort(sortByName), (indexer) => {
|
||||
return {
|
||||
key: indexer.id,
|
||||
value: indexer.name
|
||||
};
|
||||
});
|
||||
|
||||
if (includeAny) {
|
||||
values.unshift({
|
||||
key: 0,
|
||||
value: '(Any)'
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
values
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
dispatchFetchIndexers: fetchIndexers
|
||||
};
|
||||
|
||||
class IndexerSelectInputConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
if (!this.props.isPopulated) {
|
||||
this.props.dispatchFetchIndexers();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onChange = ({ name, value }) => {
|
||||
this.props.onChange({ name, value: parseInt(value) });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<EnhancedSelectInput
|
||||
{...this.props}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
IndexerSelectInputConnector.propTypes = {
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
|
||||
values: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
includeAny: PropTypes.bool.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
dispatchFetchIndexers: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
IndexerSelectInputConnector.defaultProps = {
|
||||
includeAny: false
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(IndexerSelectInputConnector);
|
||||
@@ -98,9 +98,7 @@ class KeyValueListInput extends Component {
|
||||
className,
|
||||
value,
|
||||
keyPlaceholder,
|
||||
valuePlaceholder,
|
||||
hasError,
|
||||
hasWarning
|
||||
valuePlaceholder
|
||||
} = this.props;
|
||||
|
||||
const { isFocused } = this.state;
|
||||
@@ -108,9 +106,7 @@ class KeyValueListInput extends Component {
|
||||
return (
|
||||
<div className={classNames(
|
||||
className,
|
||||
isFocused && styles.isFocused,
|
||||
hasError && styles.hasError,
|
||||
hasWarning && styles.hasWarning
|
||||
isFocused && styles.isFocused
|
||||
)}
|
||||
>
|
||||
{
|
||||
|
||||
@@ -8,16 +8,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.inputWrapper {
|
||||
flex: 1 0 0;
|
||||
}
|
||||
|
||||
.buttonWrapper {
|
||||
flex: 0 0 22px;
|
||||
}
|
||||
|
||||
.keyInput,
|
||||
.valueInput {
|
||||
width: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
@@ -63,41 +63,34 @@ class KeyValueListInputItem extends Component {
|
||||
|
||||
return (
|
||||
<div className={styles.itemContainer}>
|
||||
<div className={styles.inputWrapper}>
|
||||
<TextInput
|
||||
className={styles.keyInput}
|
||||
name="key"
|
||||
value={keyValue}
|
||||
placeholder={keyPlaceholder}
|
||||
onChange={this.onKeyChange}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
/>
|
||||
</div>
|
||||
<TextInput
|
||||
className={styles.keyInput}
|
||||
name="key"
|
||||
value={keyValue}
|
||||
placeholder={keyPlaceholder}
|
||||
onChange={this.onKeyChange}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
/>
|
||||
|
||||
<div className={styles.inputWrapper}>
|
||||
<TextInput
|
||||
className={styles.valueInput}
|
||||
name="value"
|
||||
value={value}
|
||||
placeholder={valuePlaceholder}
|
||||
onChange={this.onValueChange}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
/>
|
||||
</div>
|
||||
<TextInput
|
||||
className={styles.valueInput}
|
||||
name="value"
|
||||
value={value}
|
||||
placeholder={valuePlaceholder}
|
||||
onChange={this.onValueChange}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
/>
|
||||
|
||||
<div className={styles.buttonWrapper}>
|
||||
{
|
||||
isNew ?
|
||||
null :
|
||||
<IconButton
|
||||
name={icons.REMOVE}
|
||||
tabIndex={-1}
|
||||
onPress={this.onRemovePress}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
{
|
||||
!isNew &&
|
||||
<IconButton
|
||||
name={icons.REMOVE}
|
||||
tabIndex={-1}
|
||||
onPress={this.onRemovePress}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,16 +4,15 @@ import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import sortByName from 'Utilities/Array/sortByName';
|
||||
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
||||
import SelectInput from './SelectInput';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createSortedSectionSelector('settings.languageProfiles', sortByName),
|
||||
(state) => state.settings.languageProfiles,
|
||||
(state, { includeNoChange }) => includeNoChange,
|
||||
(state, { includeMixed }) => includeMixed,
|
||||
(languageProfiles, includeNoChange, includeMixed) => {
|
||||
const values = _.map(languageProfiles.items, (languageProfile) => {
|
||||
const values = _.map(languageProfiles.items.sort(sortByName), (languageProfile) => {
|
||||
return {
|
||||
key: languageProfile.id,
|
||||
value: languageProfile.name
|
||||
|
||||
@@ -18,19 +18,10 @@ class PathInput extends Component {
|
||||
this._node = document.getElementById('portal-root');
|
||||
|
||||
this.state = {
|
||||
value: props.value,
|
||||
isFileBrowserModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { value } = this.props;
|
||||
|
||||
if (prevProps.value !== value) {
|
||||
this.setState({ value });
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
@@ -60,8 +51,11 @@ class PathInput extends Component {
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onInputChange = ({ value }) => {
|
||||
this.setState({ value });
|
||||
onInputChange = (event, { newValue }) => {
|
||||
this.props.onChange({
|
||||
name: this.props.name,
|
||||
value: newValue
|
||||
});
|
||||
}
|
||||
|
||||
onInputKeyDown = (event) => {
|
||||
@@ -83,11 +77,6 @@ class PathInput extends Component {
|
||||
}
|
||||
|
||||
onInputBlur = () => {
|
||||
this.props.onChange({
|
||||
name: this.props.name,
|
||||
value: this.state.value
|
||||
});
|
||||
|
||||
this.props.onClearPaths();
|
||||
}
|
||||
|
||||
@@ -119,18 +108,13 @@ class PathInput extends Component {
|
||||
const {
|
||||
className,
|
||||
name,
|
||||
value,
|
||||
paths,
|
||||
includeFiles,
|
||||
hasFileBrowser,
|
||||
onChange,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
value,
|
||||
isFileBrowserModalOpen
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<AutoSuggestInput
|
||||
@@ -146,7 +130,7 @@ class PathInput extends Component {
|
||||
onSuggestionSelected={this.onSuggestionSelected}
|
||||
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
|
||||
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
||||
onChange={this.onInputChange}
|
||||
onChange={onChange}
|
||||
/>
|
||||
|
||||
{
|
||||
@@ -160,7 +144,7 @@ class PathInput extends Component {
|
||||
</FormInputButton>
|
||||
|
||||
<FileBrowserModal
|
||||
isOpen={isFileBrowserModalOpen}
|
||||
isOpen={this.state.isFileBrowserModalOpen}
|
||||
name={name}
|
||||
value={value}
|
||||
includeFiles={includeFiles}
|
||||
|
||||
@@ -20,7 +20,7 @@ function getType(type) {
|
||||
return inputTypes.NUMBER;
|
||||
case 'path':
|
||||
return inputTypes.PATH;
|
||||
case 'filePath':
|
||||
case 'filepath':
|
||||
return inputTypes.PATH;
|
||||
case 'select':
|
||||
return inputTypes.SELECT;
|
||||
@@ -28,7 +28,7 @@ function getType(type) {
|
||||
return inputTypes.TEXT_TAG;
|
||||
case 'textbox':
|
||||
return inputTypes.TEXT;
|
||||
case 'oAuth':
|
||||
case 'oauth':
|
||||
return inputTypes.OAUTH;
|
||||
default:
|
||||
return inputTypes.TEXT;
|
||||
@@ -60,7 +60,6 @@ function ProviderFieldFormGroup(props) {
|
||||
value,
|
||||
type,
|
||||
advanced,
|
||||
hidden,
|
||||
pending,
|
||||
errors,
|
||||
warnings,
|
||||
@@ -69,13 +68,6 @@ function ProviderFieldFormGroup(props) {
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
if (
|
||||
hidden === 'hidden' ||
|
||||
(hidden === 'hiddenIfNotSet' && !value)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
@@ -94,7 +86,7 @@ function ProviderFieldFormGroup(props) {
|
||||
errors={errors}
|
||||
warnings={warnings}
|
||||
pending={pending}
|
||||
includeFiles={type === 'filePath' ? true : undefined}
|
||||
includeFiles={type === 'filepath' ? true : undefined}
|
||||
onChange={onChange}
|
||||
{...otherProps}
|
||||
/>
|
||||
@@ -116,7 +108,6 @@ ProviderFieldFormGroup.propTypes = {
|
||||
value: PropTypes.any,
|
||||
type: PropTypes.string.isRequired,
|
||||
advanced: PropTypes.bool.isRequired,
|
||||
hidden: PropTypes.string,
|
||||
pending: PropTypes.bool.isRequired,
|
||||
errors: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
warnings: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
|
||||
@@ -4,16 +4,15 @@ import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import sortByName from 'Utilities/Array/sortByName';
|
||||
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
||||
import SelectInput from './SelectInput';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createSortedSectionSelector('settings.qualityProfiles', sortByName),
|
||||
(state) => state.settings.qualityProfiles,
|
||||
(state, { includeNoChange }) => includeNoChange,
|
||||
(state, { includeMixed }) => includeMixed,
|
||||
(qualityProfiles, includeNoChange, includeMixed) => {
|
||||
const values = _.map(qualityProfiles.items, (qualityProfile) => {
|
||||
const values = _.map(qualityProfiles.items.sort(sortByName), (qualityProfile) => {
|
||||
return {
|
||||
key: qualityProfile.id,
|
||||
value: qualityProfile.name
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
@@ -12,7 +13,7 @@ function createMapStateToProps() {
|
||||
(state) => state.rootFolders,
|
||||
(state, { includeNoChange }) => includeNoChange,
|
||||
(rootFolders, includeNoChange) => {
|
||||
const values = rootFolders.items.map((rootFolder) => {
|
||||
const values = _.map(rootFolders.items, (rootFolder) => {
|
||||
return {
|
||||
key: rootFolder.path,
|
||||
value: rootFolder.path,
|
||||
@@ -84,7 +85,7 @@ class RootFolderSelectInputConnector extends Component {
|
||||
onChange
|
||||
} = this.props;
|
||||
|
||||
if (!value || !values.some((v) => v.key === value) || value === ADD_NEW_KEY) {
|
||||
if (!value || !_.some(values, (v) => v.key === value) || value === ADD_NEW_KEY) {
|
||||
const defaultValue = values[0];
|
||||
|
||||
if (defaultValue.key === ADD_NEW_KEY) {
|
||||
@@ -95,27 +96,6 @@ class RootFolderSelectInputConnector extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
name,
|
||||
value,
|
||||
values,
|
||||
onChange
|
||||
} = this.props;
|
||||
|
||||
if (prevProps.values === values) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!value && values.length && values.some((v) => !!v.key && v.key !== ADD_NEW_KEY)) {
|
||||
const defaultValue = values[0];
|
||||
|
||||
if (defaultValue.key !== ADD_NEW_KEY) {
|
||||
onChange({ name, value: defaultValue.key });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
|
||||
@@ -13,15 +13,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.value {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.seriesFolder {
|
||||
flex: 0 0 auto;
|
||||
color: $disabledColor;
|
||||
}
|
||||
|
||||
.freeSpace {
|
||||
margin-left: 15px;
|
||||
color: $darkGray;
|
||||
|
||||
@@ -7,20 +7,14 @@ import styles from './RootFolderSelectInputOption.css';
|
||||
|
||||
function RootFolderSelectInputOption(props) {
|
||||
const {
|
||||
id,
|
||||
value,
|
||||
freeSpace,
|
||||
seriesFolder,
|
||||
isMobile,
|
||||
isWindows,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
const slashCharacter = isWindows ? '\\' : '/';
|
||||
|
||||
return (
|
||||
<EnhancedSelectInputOption
|
||||
id={id}
|
||||
isMobile={isMobile}
|
||||
{...otherProps}
|
||||
>
|
||||
@@ -29,18 +23,7 @@ function RootFolderSelectInputOption(props) {
|
||||
isMobile && styles.isMobile
|
||||
)}
|
||||
>
|
||||
<div className={styles.value}>
|
||||
{value}
|
||||
|
||||
{
|
||||
seriesFolder && id !== 'addNew' ?
|
||||
<div className={styles.seriesFolder}>
|
||||
{slashCharacter}
|
||||
{seriesFolder}
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
<div>{value}</div>
|
||||
|
||||
{
|
||||
freeSpace != null &&
|
||||
@@ -54,12 +37,9 @@ function RootFolderSelectInputOption(props) {
|
||||
}
|
||||
|
||||
RootFolderSelectInputOption.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
freeSpace: PropTypes.number,
|
||||
seriesFolder: PropTypes.string,
|
||||
isMobile: PropTypes.bool.isRequired,
|
||||
isWindows: PropTypes.bool
|
||||
isMobile: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
export default RootFolderSelectInputOption;
|
||||
|
||||
@@ -7,20 +7,10 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pathContainer {
|
||||
@add-mixin truncate;
|
||||
display: flex;
|
||||
flex: 1 0 0;
|
||||
}
|
||||
|
||||
.path {
|
||||
@add-mixin truncate;
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
.seriesFolder {
|
||||
flex: 0 1 auto;
|
||||
color: $disabledColor;
|
||||
flex: 1 0 0;
|
||||
}
|
||||
|
||||
.freeSpace {
|
||||
|
||||
@@ -8,32 +8,17 @@ function RootFolderSelectInputSelectedValue(props) {
|
||||
const {
|
||||
value,
|
||||
freeSpace,
|
||||
seriesFolder,
|
||||
includeFreeSpace,
|
||||
isWindows,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
const slashCharacter = isWindows ? '\\' : '/';
|
||||
|
||||
return (
|
||||
<EnhancedSelectInputSelectedValue
|
||||
className={styles.selectedValue}
|
||||
{...otherProps}
|
||||
>
|
||||
<div className={styles.pathContainer}>
|
||||
<div className={styles.path}>
|
||||
{value}
|
||||
</div>
|
||||
|
||||
{
|
||||
seriesFolder ?
|
||||
<div className={styles.seriesFolder}>
|
||||
{slashCharacter}
|
||||
{seriesFolder}
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
<div className={styles.path}>
|
||||
{value}
|
||||
</div>
|
||||
|
||||
{
|
||||
@@ -49,8 +34,6 @@ function RootFolderSelectInputSelectedValue(props) {
|
||||
RootFolderSelectInputSelectedValue.propTypes = {
|
||||
value: PropTypes.string,
|
||||
freeSpace: PropTypes.number,
|
||||
seriesFolder: PropTypes.string,
|
||||
isWindows: PropTypes.bool,
|
||||
includeFreeSpace: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import * as seriesTypes from 'Utilities/Series/seriesTypes';
|
||||
import SelectInput from './SelectInput';
|
||||
|
||||
const seriesTypeOptions = [
|
||||
{ key: seriesTypes.STANDARD, value: 'Standard' },
|
||||
{ key: seriesTypes.DAILY, value: 'Daily' },
|
||||
{ key: seriesTypes.ANIME, value: 'Anime' }
|
||||
{ key: 'standard', value: 'Standard' },
|
||||
{ key: 'daily', value: 'Daily' },
|
||||
{ key: 'anime', value: 'Anime' }
|
||||
];
|
||||
|
||||
function SeriesTypeSelectInput(props) {
|
||||
|
||||
@@ -12,20 +12,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
.hasError {
|
||||
composes: hasError from '~Components/Form/Input.css';
|
||||
}
|
||||
|
||||
.hasWarning {
|
||||
composes: hasWarning from '~Components/Form/Input.css';
|
||||
}
|
||||
|
||||
.internalInput {
|
||||
flex: 1 1 0%;
|
||||
margin-left: 3px;
|
||||
min-width: 20%;
|
||||
max-width: 100%;
|
||||
width: 0%;
|
||||
height: 31px;
|
||||
height: 21px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
@@ -100,9 +100,9 @@ class TagInput extends Component {
|
||||
suggestions
|
||||
} = this.state;
|
||||
|
||||
const key = event.key;
|
||||
const keyCode = event.keyCode;
|
||||
|
||||
if (key === 'Backspace' && !value.length) {
|
||||
if (keyCode === 8 && !value.length) {
|
||||
const index = tags.length - 1;
|
||||
|
||||
if (index >= 0) {
|
||||
@@ -116,7 +116,7 @@ class TagInput extends Component {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (delimiters.includes(key)) {
|
||||
if (delimiters.includes(keyCode)) {
|
||||
const selectedIndex = this._autosuggestRef.highlightedSuggestionIndex;
|
||||
const tag = getTag(value, selectedIndex, suggestions, allowNew);
|
||||
|
||||
@@ -210,8 +210,6 @@ class TagInput extends Component {
|
||||
const {
|
||||
className,
|
||||
inputContainerClassName,
|
||||
hasError,
|
||||
hasWarning,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
@@ -229,8 +227,6 @@ class TagInput extends Component {
|
||||
inputContainerClassName={classNames(
|
||||
inputContainerClassName,
|
||||
isFocused && styles.isFocused,
|
||||
hasError && styles.hasError,
|
||||
hasWarning && styles.hasWarning
|
||||
)}
|
||||
value={value}
|
||||
suggestions={suggestions}
|
||||
@@ -260,11 +256,11 @@ TagInput.propTypes = {
|
||||
allowNew: PropTypes.bool.isRequired,
|
||||
kind: PropTypes.oneOf(kinds.all).isRequired,
|
||||
placeholder: PropTypes.string.isRequired,
|
||||
delimiters: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
delimiters: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
minQueryLength: PropTypes.number.isRequired,
|
||||
hasError: PropTypes.bool,
|
||||
hasWarning: PropTypes.bool,
|
||||
tagComponent: PropTypes.elementType.isRequired,
|
||||
tagComponent: PropTypes.func.isRequired,
|
||||
onTagAdd: PropTypes.func.isRequired,
|
||||
onTagDelete: PropTypes.func.isRequired
|
||||
};
|
||||
@@ -275,7 +271,8 @@ TagInput.defaultProps = {
|
||||
allowNew: true,
|
||||
kind: kinds.INFO,
|
||||
placeholder: '',
|
||||
delimiters: ['Tab', 'Enter', ' ', ','],
|
||||
// Tab, enter, space and comma
|
||||
delimiters: [9, 13, 32, 188],
|
||||
minQueryLength: 1,
|
||||
tagComponent: TagInputTag
|
||||
};
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
bottom: -1px;
|
||||
left: -1px;
|
||||
display: flex;
|
||||
align-items: start;
|
||||
flex-wrap: wrap;
|
||||
padding: 1px 16px;
|
||||
min-height: 33px;
|
||||
padding: 6px 16px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ TagInputInput.propTypes = {
|
||||
inputProps: PropTypes.object.isRequired,
|
||||
kind: PropTypes.oneOf(kinds.all).isRequired,
|
||||
isFocused: PropTypes.bool.isRequired,
|
||||
tagComponent: PropTypes.elementType.isRequired,
|
||||
tagComponent: PropTypes.func.isRequired,
|
||||
onTagDelete: PropTypes.func.isRequired,
|
||||
onInputContainerPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
.tag {
|
||||
composes: link from '~Components/Link/Link.css';
|
||||
|
||||
height: 31px;
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import { kinds } from 'Helpers/Props';
|
||||
import tagShape from 'Helpers/Props/Shapes/tagShape';
|
||||
import Label from 'Components/Label';
|
||||
import Link from 'Components/Link/Link';
|
||||
import styles from './TagInputTag.css';
|
||||
|
||||
class TagInputTag extends Component {
|
||||
|
||||
@@ -32,9 +31,9 @@ class TagInputTag extends Component {
|
||||
tag,
|
||||
kind
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Link
|
||||
className={styles.tag}
|
||||
tabIndex={-1}
|
||||
onPress={this.onDelete}
|
||||
>
|
||||
|
||||
@@ -25,7 +25,3 @@
|
||||
.warning {
|
||||
color: $warningColor;
|
||||
}
|
||||
|
||||
.purple {
|
||||
color: $purple;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import styles from './LoadingIndicator.css';
|
||||
|
||||
function LoadingIndicator({ className, rippleClassName, size }) {
|
||||
function LoadingIndicator({ className, size }) {
|
||||
const sizeInPx = `${size}px`;
|
||||
const width = sizeInPx;
|
||||
const height = sizeInPx;
|
||||
@@ -17,17 +17,17 @@ function LoadingIndicator({ className, rippleClassName, size }) {
|
||||
style={{ width, height }}
|
||||
>
|
||||
<div
|
||||
className={rippleClassName}
|
||||
className={styles.ripple}
|
||||
style={{ width, height }}
|
||||
/>
|
||||
|
||||
<div
|
||||
className={rippleClassName}
|
||||
className={styles.ripple}
|
||||
style={{ width, height }}
|
||||
/>
|
||||
|
||||
<div
|
||||
className={rippleClassName}
|
||||
className={styles.ripple}
|
||||
style={{ width, height }}
|
||||
/>
|
||||
</div>
|
||||
@@ -37,13 +37,11 @@ function LoadingIndicator({ className, rippleClassName, size }) {
|
||||
|
||||
LoadingIndicator.propTypes = {
|
||||
className: PropTypes.string,
|
||||
rippleClassName: PropTypes.string,
|
||||
size: PropTypes.number
|
||||
};
|
||||
|
||||
LoadingIndicator.defaultProps = {
|
||||
className: styles.loading,
|
||||
rippleClassName: styles.ripple,
|
||||
size: 50
|
||||
};
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user