mirror of
https://github.com/Sonarr/Sonarr.git
synced 2026-04-18 21:35:27 -04:00
Compare commits
234 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 473b146ae2 | |||
| bdc0bb4441 | |||
| 96c5a37ca8 | |||
| ce0b7e1077 | |||
| 29cde083f9 | |||
| 8faebc01ee | |||
| 2ea154b863 | |||
| d02dfc9ff1 | |||
| 7bcfa3d7b2 | |||
| 1c1f9cddff | |||
| 8308d65375 | |||
| d64d59ff27 | |||
| 8da6f7d7f4 | |||
| b86cfd49ef | |||
| d421ff9736 | |||
| 92c61701f2 | |||
| d45d9e356c | |||
| 098f9a2675 | |||
| 0347dab82e | |||
| 9aa89a0df9 | |||
| 556bd11725 | |||
| 07f5c21a07 | |||
| 93b20960b8 | |||
| 3cbdd6bfd3 | |||
| c3c38880e6 | |||
| 415bbf5b3b | |||
| 186cb02748 | |||
| 4aaccb909f | |||
| 2daf7dd01a | |||
| ab9ed73e55 | |||
| a4a33fe167 | |||
| e6fbd10031 | |||
| 9868d96fec | |||
| 0d1c2ac40c | |||
| a6d0dddaf7 | |||
| 06d57e8f32 | |||
| 70a40edc5d | |||
| 95d64208d0 | |||
| e28b2e8328 | |||
| 4123745a6b | |||
| 70bb4d71e6 | |||
| dd314e1741 | |||
| 3cbb489ac6 | |||
| 101df4cbf1 | |||
| 42263a0ec0 | |||
| d402f7514e | |||
| 84e6674e23 | |||
| afcfaace19 | |||
| 5a3bd8cfe5 | |||
| f0c90a4744 | |||
| 3baed292e1 | |||
| d41a2cad73 | |||
| ffccc3be38 | |||
| ef1e8d7ef3 | |||
| 7af891216d | |||
| aa80500b35 | |||
| b72fbe06f7 | |||
| 41a63a5418 | |||
| e8ce7898c1 | |||
| 687a45c564 | |||
| 3ac3dd3ca5 | |||
| f2efebf7d9 | |||
| 7b68ce49d5 | |||
| 8a2a41fab0 | |||
| ceaaec5378 | |||
| e49a3e7206 | |||
| dc7986dbad | |||
| 2dfba130f5 | |||
| 155c7c409b | |||
| aacb8970f8 | |||
| be66a0520d | |||
| 3fa3c45794 | |||
| 0f6da1873e | |||
| 1564208e83 | |||
| e724e8db60 | |||
| 5a092a83cd | |||
| ffefe5e8aa | |||
| 631fdd8a26 | |||
| 53d7ef4014 | |||
| 5c3ac79043 | |||
| 90fb1646e0 | |||
| 54604e45e0 | |||
| 9ed0f9eee8 | |||
| b764c44318 | |||
| adbd519061 | |||
| b0415299ca | |||
| e96d05149c | |||
| 354ddcfee5 | |||
| 6d232778e2 | |||
| 95ee7daf21 | |||
| 2238ac5d17 | |||
| c209c1c034 | |||
| b1eec16333 | |||
| e126c45fb3 | |||
| c89ff93be4 | |||
| c82c27a5c5 | |||
| b3e84f407a | |||
| 72902c8984 | |||
| 2c47c5eb99 | |||
| 398129f3e1 | |||
| d74ab12d9e | |||
| 679c0599dd | |||
| 4d04ad5632 | |||
| 3fdc50b354 | |||
| 7eeff32185 | |||
| d40f2cb852 | |||
| f9dc2fb6d5 | |||
| de31dfb11e | |||
| ef6a648189 | |||
| 09953e2af8 | |||
| be240119e8 | |||
| 2b7893c834 | |||
| 896e824ca1 | |||
| 7a94725808 | |||
| a66fb76e9a | |||
| b453d48fee | |||
| a7f2c07998 | |||
| 3cff878f74 | |||
| 665d536481 | |||
| ec6d407fbb | |||
| 72bc7ed6d4 | |||
| ac407ca2c0 | |||
| 6af5f2b528 | |||
| 8fd4a98fbe | |||
| 07d553fae3 | |||
| 34e0eea173 | |||
| 73e6db9a12 | |||
| 0df464ac03 | |||
| 78ee6afbae | |||
| 31be74e6d3 | |||
| 767a09894a | |||
| 0d410d107d | |||
| 7829b18b3c | |||
| b2267a55ce | |||
| eca016fe61 | |||
| 81723f7fa9 | |||
| 44c91fb90c | |||
| 7cb5bd9c95 | |||
| 8196f6b9db | |||
| d72b16531b | |||
| f333196efe | |||
| 6ea047dcb4 | |||
| ea65867b23 | |||
| c41200e762 | |||
| 4c70afbb53 | |||
| 0c1ce66053 | |||
| fa8b8cebf9 | |||
| 72fa89ba76 | |||
| dc7b4cebf2 | |||
| 3c1dd94915 | |||
| 3decbbac3a | |||
| 27f43569f5 | |||
| bd9bded73b | |||
| 0ce81e1ab6 | |||
| 2926201694 | |||
| 059be2c853 | |||
| dd09f31abb | |||
| 1da20da3ff | |||
| 341773830b | |||
| c65452bb01 | |||
| 34d81356a3 | |||
| c9b84a5202 | |||
| 5394cc2dc9 | |||
| 63141f339f | |||
| 079a0b56c3 | |||
| 66721affe7 | |||
| d8ab23e9ba | |||
| 11b0a5c9cc | |||
| d273a72cb3 | |||
| 08641a6694 | |||
| 9ac5c0d886 | |||
| 8a57d33223 | |||
| 1945b53e43 | |||
| 161e1820a8 | |||
| 22b5ac8622 | |||
| f75a70e42b | |||
| c9076606be | |||
| 853d22b947 | |||
| 82d6719d91 | |||
| 3f02c150e3 | |||
| d7d46a93a7 | |||
| dfd51635a6 | |||
| c04366d505 | |||
| fd89e88d40 | |||
| 894de923b9 | |||
| c47e7cd91d | |||
| e359347a3b | |||
| d320017e3c | |||
| e6c34f4311 | |||
| 9b0c945086 | |||
| bc85f5de1d | |||
| 4df219161c | |||
| 06f157e634 | |||
| 54addbdd28 | |||
| 0e721917e7 | |||
| 4dc7089f89 | |||
| 30b5a35db2 | |||
| dc8f81b536 | |||
| a018770a18 | |||
| 5e4f7c5d8e | |||
| 1d9d665ed0 | |||
| 68477c09a7 | |||
| 574b9086d4 | |||
| d74c323c66 | |||
| 8e85a1b84e | |||
| 6ce1cb4325 | |||
| fbbd85d8b2 | |||
| e611dc43c5 | |||
| c86309cfc0 | |||
| ec74e9bce0 | |||
| f371e8a523 | |||
| 57a059eecb | |||
| 2cb149c647 | |||
| ee5371b582 | |||
| 70e4dbe3bd | |||
| 18ead9a64f | |||
| d2764cee2a | |||
| 082c098420 | |||
| 46a42e2901 | |||
| c21cacd309 | |||
| 81ac359f71 | |||
| f5b91c90bc | |||
| 0a92a3012e | |||
| ff8fc237e2 | |||
| b99d943b4d | |||
| 3199fe08e8 | |||
| 7503ce62af | |||
| df8ca250aa | |||
| c71b4bde86 | |||
| 3f67802e3d | |||
| 093ed23140 | |||
| 0f8dee7011 | |||
| 0cb557b716 | |||
| 8137a776b6 |
+7
-17
@@ -1,22 +1,12 @@
|
|||||||
# Auto detect text files and perform LF normalization
|
# Auto detect text files and perform LF normalization
|
||||||
*text eol=lf
|
* 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
|
||||||
|
|
||||||
# Custom for Visual Studio
|
# Custom for Visual Studio
|
||||||
*.cs diff=csharp
|
*.cs diff=csharp
|
||||||
*.sln merge=union
|
*.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
|
|
||||||
+7
-2
@@ -45,6 +45,10 @@ _dotCover*
|
|||||||
# DevExpress CodeRush
|
# DevExpress CodeRush
|
||||||
src/.cr/
|
src/.cr/
|
||||||
|
|
||||||
|
# Emacs
|
||||||
|
*~
|
||||||
|
\#*\#
|
||||||
|
|
||||||
# NCrunch
|
# NCrunch
|
||||||
*.ncrunch*
|
*.ncrunch*
|
||||||
.*crunch*.local.xml
|
.*crunch*.local.xml
|
||||||
@@ -115,7 +119,9 @@ node_modules/
|
|||||||
_output*
|
_output*
|
||||||
_rawPackage/
|
_rawPackage/
|
||||||
_dotTrace*
|
_dotTrace*
|
||||||
_tests/
|
_tests*
|
||||||
|
_publish*
|
||||||
|
_temp*
|
||||||
*.Result.xml
|
*.Result.xml
|
||||||
setup/Output/
|
setup/Output/
|
||||||
*.~is
|
*.~is
|
||||||
@@ -133,6 +139,5 @@ output/*
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
_start
|
_start
|
||||||
_temp_*/**/*
|
|
||||||
|
|
||||||
src/.idea/
|
src/.idea/
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
[submodule "src/ExternalModules/CurlSharp"]
|
|
||||||
path = src/ExternalModules/CurlSharp
|
|
||||||
url = https://github.com/Sonarr/CurlSharp.git
|
|
||||||
branch = master
|
|
||||||
@@ -20,7 +20,7 @@ Sonarr is a PVR for Usenet and BitTorrent users. It can monitor multiple RSS fee
|
|||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
* [Visual Studio 2017] (https://www.visualstudio.com/vs/)
|
* [Visual Studio 2017](https://www.visualstudio.com/vs/)
|
||||||
* [Git](https://git-scm.com/downloads)
|
* [Git](https://git-scm.com/downloads)
|
||||||
* [NodeJS](https://nodejs.org/en/download/)
|
* [NodeJS](https://nodejs.org/en/download/)
|
||||||
* [Yarn](https://yarnpkg.com/)
|
* [Yarn](https://yarnpkg.com/)
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
#! /bin/bash
|
#! /bin/bash
|
||||||
msBuildVersion='15.0'
|
msBuildVersion='15.0'
|
||||||
outputFolder='./_output'
|
outputFolder='./_output'
|
||||||
|
outputFolderWindows='./_output_windows'
|
||||||
outputFolderLinux='./_output_linux'
|
outputFolderLinux='./_output_linux'
|
||||||
outputFolderMacOS='./_output_macos'
|
outputFolderMacOS='./_output_macos'
|
||||||
outputFolderMacOSApp='./_output_macos_app'
|
outputFolderMacOSApp='./_output_macos_app'
|
||||||
testPackageFolder='./_tests/'
|
testPackageFolder='./_tests'
|
||||||
testSearchPattern='*.Test/bin/x86/Release'
|
testPackageFolderWindows='./_tests_windows'
|
||||||
|
testPackageFolderLinux='./_tests_linux'
|
||||||
sourceFolder='./src'
|
sourceFolder='./src'
|
||||||
slnFile=$sourceFolder/Sonarr.sln
|
slnFile=$sourceFolder/Sonarr.sln
|
||||||
updateFolder=$outputFolder/Sonarr.Update
|
updateSubFolder=Sonarr.Update
|
||||||
updateFolderMono=$outputFolderLinux/Sonarr.Update
|
|
||||||
|
sqlitePackageDir="$HOME/.nuget/packages/system.data.sqlite.core.lidarr/1.0.111-5"
|
||||||
|
|
||||||
nuget='tools/nuget/nuget.exe';
|
nuget='tools/nuget/nuget.exe';
|
||||||
vswhere='tools/vswhere/vswhere.exe';
|
vswhere='tools/vswhere/vswhere.exe';
|
||||||
@@ -47,7 +50,8 @@ UpdateVersionNumber()
|
|||||||
verBuild=`echo "${BUILD_NUMBER}" | cut -d. -f4`
|
verBuild=`echo "${BUILD_NUMBER}" | cut -d. -f4`
|
||||||
BUILD_NUMBER=$verMajorMinorRevision.$verBuild
|
BUILD_NUMBER=$verMajorMinorRevision.$verBuild
|
||||||
echo "##teamcity[buildNumber '$BUILD_NUMBER']"
|
echo "##teamcity[buildNumber '$BUILD_NUMBER']"
|
||||||
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
|
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
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,16 +90,17 @@ CleanFolder()
|
|||||||
|
|
||||||
BuildWithMSBuild()
|
BuildWithMSBuild()
|
||||||
{
|
{
|
||||||
installationPath=`$vswhere -latest -products \* -requires Microsoft.Component.MSBuild -property installationPath`
|
msBuildPath=`$vswhere -latest -products \* -requires Microsoft.Component.MSBuild -find MSBuild\\\\\*\*\\\\Bin\\\\MSBuild.exe`
|
||||||
installationPath=${installationPath/C:\\/\/c\/}
|
msBuildPath=${msBuildPath/C:\\/\/c\/}
|
||||||
installationPath=${installationPath//\\/\/}
|
msBuildPath=${msBuildPath//\\/\/}
|
||||||
msBuild="$installationPath/MSBuild/$msBuildVersion/Bin"
|
msBuildDir=$(dirname "$msBuildPath")
|
||||||
echo $msBuild
|
|
||||||
|
|
||||||
export PATH=$msBuild:$PATH
|
echo $msBuildDir
|
||||||
CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x86 //t:Clean //m
|
|
||||||
|
export PATH=$msBuildDir:$PATH
|
||||||
|
CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x64 //t:Clean //m
|
||||||
$nuget restore $slnFile
|
$nuget restore $slnFile
|
||||||
CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x86 //t:Build //m //p:AllowedReferenceRelatedFileExtensions=.pdb
|
CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x64 //t:Build //m //p:AllowedReferenceRelatedFileExtensions=.pdb
|
||||||
}
|
}
|
||||||
|
|
||||||
BuildWithXbuild()
|
BuildWithXbuild()
|
||||||
@@ -103,13 +108,13 @@ BuildWithXbuild()
|
|||||||
export MONO_IOMAP=case
|
export MONO_IOMAP=case
|
||||||
CheckExitCode xbuild /t:Clean $slnFile
|
CheckExitCode xbuild /t:Clean $slnFile
|
||||||
mono $nuget restore $slnFile
|
mono $nuget restore $slnFile
|
||||||
CheckExitCode xbuild /p:Configuration=Release /p:Platform=x86 /t:Build /p:AllowedReferenceRelatedFileExtensions=.pdb $slnFile
|
CheckExitCode xbuild /p:Configuration=Release /p:Platform=x64 /t:Build /p:AllowedReferenceRelatedFileExtensions=.pdb $slnFile
|
||||||
}
|
}
|
||||||
|
|
||||||
LintUI()
|
LintUI()
|
||||||
{
|
{
|
||||||
ProgressStart 'ESLint'
|
ProgressStart 'ESLint'
|
||||||
CheckExitCode yarn eslint
|
CheckExitCode yarn lint
|
||||||
ProgressEnd 'ESLint'
|
ProgressEnd 'ESLint'
|
||||||
|
|
||||||
ProgressStart 'Stylelint'
|
ProgressStart 'Stylelint'
|
||||||
@@ -122,6 +127,7 @@ Build()
|
|||||||
ProgressStart 'Build'
|
ProgressStart 'Build'
|
||||||
|
|
||||||
rm -rf $outputFolder
|
rm -rf $outputFolder
|
||||||
|
rm -rf $testPackageFolder
|
||||||
|
|
||||||
if [ $runtime = "dotnet" ] ; then
|
if [ $runtime = "dotnet" ] ; then
|
||||||
BuildWithMSBuild
|
BuildWithMSBuild
|
||||||
@@ -131,9 +137,6 @@ Build()
|
|||||||
|
|
||||||
CleanFolder $outputFolder false
|
CleanFolder $outputFolder false
|
||||||
|
|
||||||
echo "Removing Mono.Posix.dll"
|
|
||||||
rm $outputFolder/Mono.Posix.dll
|
|
||||||
|
|
||||||
ProgressEnd 'Build'
|
ProgressEnd 'Build'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,6 +170,36 @@ CreateMdbs()
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PatchMono()
|
||||||
|
{
|
||||||
|
local path=$1
|
||||||
|
|
||||||
|
# Copy over the netstandard.dll facade since mono has no separate package for it and includes it in mono-devel
|
||||||
|
for assembly in netstandard System.Runtime
|
||||||
|
do
|
||||||
|
echo "Copy Mono-specific facade $assembly.dll"
|
||||||
|
cp $sourceFolder/Libraries/Mono/$assembly.dll $path/$assembly.dll
|
||||||
|
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()
|
PackageMono()
|
||||||
{
|
{
|
||||||
ProgressStart 'Creating Mono Package'
|
ProgressStart 'Creating Mono Package'
|
||||||
@@ -190,16 +223,11 @@ PackageMono()
|
|||||||
rm -f $outputFolderLinux/sqlite3.*
|
rm -f $outputFolderLinux/sqlite3.*
|
||||||
rm -f $outputFolderLinux/MediaInfo.*
|
rm -f $outputFolderLinux/MediaInfo.*
|
||||||
|
|
||||||
|
PatchMono $outputFolderLinux
|
||||||
|
|
||||||
echo "Adding Sonarr.Core.dll.config (for dllmap)"
|
echo "Adding Sonarr.Core.dll.config (for dllmap)"
|
||||||
cp $sourceFolder/NzbDrone.Core/Sonarr.Core.dll.config $outputFolderLinux
|
cp $sourceFolder/NzbDrone.Core/Sonarr.Core.dll.config $outputFolderLinux
|
||||||
|
|
||||||
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"
|
echo "Renaming Sonarr.Console.exe to Sonarr.exe"
|
||||||
rm $outputFolderLinux/Sonarr.exe*
|
rm $outputFolderLinux/Sonarr.exe*
|
||||||
for file in $outputFolderLinux/Sonarr.Console.exe*; do
|
for file in $outputFolderLinux/Sonarr.Console.exe*; do
|
||||||
@@ -210,7 +238,7 @@ PackageMono()
|
|||||||
rm $outputFolderLinux/Sonarr.Windows.*
|
rm $outputFolderLinux/Sonarr.Windows.*
|
||||||
|
|
||||||
echo "Adding Sonarr.Mono to UpdatePackage"
|
echo "Adding Sonarr.Mono to UpdatePackage"
|
||||||
cp $outputFolderLinux/Sonarr.Mono.* $updateFolderMono
|
cp $outputFolderLinux/Sonarr.Mono.* $outputFolderLinux/$updateSubFolder/
|
||||||
|
|
||||||
ProgressEnd 'Creating Mono Package'
|
ProgressEnd 'Creating Mono Package'
|
||||||
}
|
}
|
||||||
@@ -229,11 +257,11 @@ PackageMacOS()
|
|||||||
echo "Copying Binaries"
|
echo "Copying Binaries"
|
||||||
cp -r $outputFolderLinux/* $outputFolderMacOS
|
cp -r $outputFolderLinux/* $outputFolderMacOS
|
||||||
|
|
||||||
echo "Adding sqlite dylibs"
|
echo "Adding sqlite dylib"
|
||||||
cp $sourceFolder/Libraries/Sqlite/*.dylib $outputFolderMacOS
|
cp "$sqlitePackageDir/runtimes/osx-x64/native/net46"/* $outputFolderMacOS
|
||||||
|
|
||||||
echo "Adding MediaInfo dylib"
|
echo "Adding MediaInfo dylib"
|
||||||
cp $sourceFolder/Libraries/MediaInfo/*.dylib $outputFolderMacOS
|
cp $sourceFolder/Libraries/MediaInfo/x64/*.dylib $outputFolderMacOS
|
||||||
|
|
||||||
ProgressEnd 'Creating MacOS Package'
|
ProgressEnd 'Creating MacOS Package'
|
||||||
}
|
}
|
||||||
@@ -254,11 +282,11 @@ PackageMacOSApp()
|
|||||||
echo "Copying Binaries"
|
echo "Copying Binaries"
|
||||||
cp -r $outputFolderLinux/* $outputFolderMacOSApp/Sonarr.app/Contents/MacOS
|
cp -r $outputFolderLinux/* $outputFolderMacOSApp/Sonarr.app/Contents/MacOS
|
||||||
|
|
||||||
echo "Adding sqlite dylibs"
|
echo "Adding sqlite dylib"
|
||||||
cp $sourceFolder/Libraries/Sqlite/*.dylib $outputFolderMacOSApp/Sonarr.app/Contents/MacOS
|
cp "$sqlitePackageDir/runtimes/osx-x64/native/net46"/* $outputFolderMacOS
|
||||||
|
|
||||||
echo "Adding MediaInfo dylib"
|
echo "Adding MediaInfo dylib"
|
||||||
cp $sourceFolder/Libraries/MediaInfo/*.dylib $outputFolderMacOSApp/Sonarr.app/Contents/MacOS
|
cp $sourceFolder/Libraries/MediaInfo/x64/*.dylib $outputFolderMacOS
|
||||||
|
|
||||||
echo "Removing Update Folder"
|
echo "Removing Update Folder"
|
||||||
rm -r $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/Sonarr.Update
|
rm -r $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/Sonarr.Update
|
||||||
@@ -266,54 +294,88 @@ PackageMacOSApp()
|
|||||||
ProgressEnd 'Creating macOS App Package'
|
ProgressEnd 'Creating macOS App Package'
|
||||||
}
|
}
|
||||||
|
|
||||||
PackageTests()
|
PackageTestsMono()
|
||||||
{
|
{
|
||||||
ProgressStart 'Creating Test Package'
|
ProgressStart 'Creating Mono Test Package'
|
||||||
|
|
||||||
rm -rf $testPackageFolder
|
rm -rf $testPackageFolderLinux
|
||||||
mkdir $testPackageFolder
|
|
||||||
|
|
||||||
find $sourceFolder -path $testSearchPattern -exec cp -r -u -T "{}" $testPackageFolder \;
|
echo "Copying Binaries"
|
||||||
|
cp -r $testPackageFolder $testPackageFolderLinux
|
||||||
|
|
||||||
if [ $runtime = "dotnet" ] ; then
|
if [ $runtime = "dotnet" ] ; then
|
||||||
$nuget install NUnit.ConsoleRunner -Version 3.2.0 -Output $testPackageFolder
|
$nuget install NUnit.ConsoleRunner -Version 3.10.0 -Output $testPackageFolderLinux
|
||||||
else
|
else
|
||||||
mono $nuget install NUnit.ConsoleRunner -Version 3.2.0 -Output $testPackageFolder
|
mono $nuget install NUnit.ConsoleRunner -Version 3.10.0 -Output $testPackageFolderLinux
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cp $outputFolder/*.dll $testPackageFolder
|
echo "Creating MDBs"
|
||||||
cp ./test.sh $testPackageFolder
|
CreateMdbs $testPackageFolderLinux
|
||||||
|
|
||||||
echo "Creating MDBs for tests"
|
echo "Removing PDBs"
|
||||||
CreateMdbs $testPackageFolder
|
find $testPackageFolderLinux -name "*.pdb" -exec rm "{}" \;
|
||||||
|
|
||||||
rm -f $testPackageFolder/*.log.config
|
PatchMono $testPackageFolderLinux
|
||||||
|
|
||||||
CleanFolder $testPackageFolder true
|
|
||||||
|
|
||||||
echo "Adding Sonarr.Core.dll.config (for dllmap)"
|
echo "Adding Sonarr.Core.dll.config (for dllmap)"
|
||||||
cp $sourceFolder/NzbDrone.Core/Sonarr.Core.dll.config $testPackageFolder
|
cp $sourceFolder/NzbDrone.Core/Sonarr.Core.dll.config $testPackageFolderLinux
|
||||||
|
|
||||||
echo "Adding CurlSharp.dll.config (for dllmap)"
|
cp ./test.sh $testPackageFolderLinux/
|
||||||
cp $sourceFolder/NzbDrone.Common/CurlSharp.dll.config $testPackageFolder
|
dos2unix $testPackageFolderLinux/test.sh
|
||||||
|
|
||||||
echo "Copying CurlSharp libraries"
|
echo "Removing Sonarr.Windows"
|
||||||
cp $sourceFolder/ExternalModules/CurlSharp/libs/i386/* $testPackageFolder
|
rm $testPackageFolderLinux/Sonarr.Windows.*
|
||||||
|
|
||||||
ProgressEnd 'Creating Test Package'
|
rm -f $testPackageFolderLinux/*.log.config
|
||||||
|
|
||||||
|
CleanFolder $testPackageFolderLinux true
|
||||||
|
|
||||||
|
ProgressEnd 'Creating Linux Test Package'
|
||||||
}
|
}
|
||||||
|
|
||||||
CleanupWindowsPackage()
|
PackageTestsWindows()
|
||||||
{
|
{
|
||||||
ProgressStart 'Cleaning Windows Package'
|
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
|
||||||
|
|
||||||
echo "Removing Sonarr.Mono"
|
echo "Removing Sonarr.Mono"
|
||||||
rm -f $outputFolder/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.*
|
||||||
|
|
||||||
echo "Adding Sonarr.Windows to UpdatePackage"
|
echo "Adding Sonarr.Windows to UpdatePackage"
|
||||||
cp $outputFolder/Sonarr.Windows.* $updateFolder
|
cp $outputFolderWindows/Sonarr.Windows.* $outputFolderWindows/$updateSubFolder/
|
||||||
|
|
||||||
ProgressEnd 'Cleaning Windows Package'
|
ProgressEnd 'Creating Windows Package'
|
||||||
}
|
}
|
||||||
|
|
||||||
PublishArtifacts()
|
PublishArtifacts()
|
||||||
@@ -321,10 +383,11 @@ PublishArtifacts()
|
|||||||
ProgressStart 'Publishing Artifacts'
|
ProgressStart 'Publishing Artifacts'
|
||||||
|
|
||||||
# Tests
|
# Tests
|
||||||
echo "##teamcity[publishArtifacts '_tests/** => tests.zip']"
|
echo "##teamcity[publishArtifacts '$testPackageFolderWindows/** => tests.windows.zip']"
|
||||||
|
echo "##teamcity[publishArtifacts '$testPackageFolderLinux/** => tests.linux.zip']"
|
||||||
|
|
||||||
# Releases
|
# Releases
|
||||||
echo "##teamcity[publishArtifacts '$outputFolder/** => Sonarr.$BRANCH.$BUILD_NUMBER.windows.zip!Sonarr']"
|
echo "##teamcity[publishArtifacts '$outputFolderWindows/** => Sonarr.$BRANCH.$BUILD_NUMBER.windows.zip!Sonarr']"
|
||||||
echo "##teamcity[publishArtifacts '$outputFolderLinux/** => Sonarr.$BRANCH.$BUILD_NUMBER.linux.tar.gz!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 '$outputFolderMacOS/** => Sonarr.$BRANCH.$BUILD_NUMBER.macos.tar.gz!Sonarr']"
|
||||||
echo "##teamcity[publishArtifacts '$outputFolderMacOSApp/** => Sonarr.$BRANCH.$BUILD_NUMBER.macos.zip']"
|
echo "##teamcity[publishArtifacts '$outputFolderMacOSApp/** => Sonarr.$BRANCH.$BUILD_NUMBER.macos.zip']"
|
||||||
@@ -354,6 +417,7 @@ RunGulp
|
|||||||
PackageMono
|
PackageMono
|
||||||
PackageMacOS
|
PackageMacOS
|
||||||
PackageMacOSApp
|
PackageMacOSApp
|
||||||
PackageTests
|
PackageTestsMono
|
||||||
CleanupWindowsPackage
|
PackageTestsWindows
|
||||||
|
PackageWindows
|
||||||
PublishArtifacts
|
PublishArtifacts
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# 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
|
||||||
+23
-7
@@ -19,7 +19,11 @@ 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 "# Do Not Edit\nPackageVersion=$BuildVersion\nReleaseVersion=$BuildVersion\nUpdateMethod=$PackageUpdater\nBranch=$BuildBranch" > package_info
|
||||||
|
|
||||||
echo Running debuild for $BuildVersion
|
echo Running debuild for $BuildVersion
|
||||||
debuild -b
|
if [ -z "${TEST_OUTPUT}" ]; then
|
||||||
|
debuild -b
|
||||||
|
else
|
||||||
|
debuild -us -uc -b
|
||||||
|
fi
|
||||||
|
|
||||||
# Restore debian directory to the original files
|
# Restore debian directory to the original files
|
||||||
rm -rf ./debian
|
rm -rf ./debian
|
||||||
@@ -32,16 +36,28 @@ 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 "# Do Not Edit\nPackageVersion=$BootstrapVersion\nReleaseVersion=$BuildVersion\nUpdateMethod=$BootstrapUpdater\nBranch=$BuildBranch" > package_info
|
||||||
|
|
||||||
echo Running debuild for $BootstrapVersion
|
echo Running debuild for $BootstrapVersion
|
||||||
debuild -b
|
if [ -z "${TEST_OUTPUT}" ]; then
|
||||||
|
debuild -b
|
||||||
|
else
|
||||||
|
debuild -us -uc -b
|
||||||
|
fi
|
||||||
|
|
||||||
echo Moving stuff around
|
echo Moving stuff around
|
||||||
mv ../sonarr_*.deb ./
|
mv ../sonarr_*.deb ./
|
||||||
mv ../sonarr_*.changes ./
|
mv ../sonarr_*.changes ./
|
||||||
rm ../sonarr_*.build
|
rm ../sonarr_*.build
|
||||||
|
|
||||||
echo Signing Package
|
if [ -z "${TEST_OUTPUT}" ]; then
|
||||||
dpkg-sig -k 884589CE --sign builder "sonarr_${BuildVersion}_all.deb"
|
echo Signing Package
|
||||||
dpkg-sig -k 884589CE --sign builder "sonarr_${BootstrapVersion}_all.deb"
|
dpkg-sig -k 884589CE --sign builder "sonarr_${BuildVersion}_all.deb"
|
||||||
|
dpkg-sig -k 884589CE --sign builder "sonarr_${BootstrapVersion}_all.deb"
|
||||||
|
|
||||||
echo running alien
|
echo running alien
|
||||||
alien -r -v ./*.deb
|
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
|
||||||
|
|||||||
@@ -7,15 +7,16 @@ Vcs-Git: git@github.com:Sonarr/Sonarr.git
|
|||||||
Vcs-Browser: https://github.com/Sonarr/Sonarr
|
Vcs-Browser: https://github.com/Sonarr/Sonarr
|
||||||
Build-Depends: debhelper (>= 9),
|
Build-Depends: debhelper (>= 9),
|
||||||
dh-systemd (>= 1.5),
|
dh-systemd (>= 1.5),
|
||||||
mono-devel (>= 4.6),
|
mono-devel (>= 5.18),
|
||||||
libmono-cil-dev (>= 4.6),
|
libmono-cil-dev (>= 5.18),
|
||||||
cli-common-dev (>= 0.5.7)
|
cli-common-dev (>= 0.9+xamarin5)
|
||||||
|
|
||||||
Package: sonarr
|
Package: sonarr
|
||||||
Architecture: all
|
Architecture: all
|
||||||
Provides: nzbdrone
|
Provides: nzbdrone
|
||||||
Conflicts: nzbdrone
|
Conflicts: nzbdrone
|
||||||
Replaces: nzbdrone
|
Replaces: nzbdrone
|
||||||
Depends: adduser, libsqlite3-0 (>= 3.7), libmediainfo0v5 (>= 0.7.52), mono-runtime (>= 5.4), ${cli:Depends}, ${misc:Depends}
|
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: sqlite3 (>= 3.7), mediainfo (>= 0.7.52), ${cli:Recommends}
|
Recommends: libmediainfo0v5 (>= 18.03) | libmediainfo0 (>= 18.03)
|
||||||
|
Suggests: sqlite3 (>= 3.7), mediainfo (>= 0.7.52)
|
||||||
Description: Internet PVR
|
Description: Internet PVR
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
sonarr_bin/* /usr/lib/sonarr/bin
|
sonarr_bin/* usr/lib/sonarr/bin
|
||||||
package_info /usr/lib/sonarr
|
package_info usr/lib/sonarr
|
||||||
|
|||||||
@@ -104,4 +104,4 @@ fi
|
|||||||
|
|
||||||
#DEBHELPER#
|
#DEBHELPER#
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -22,10 +22,25 @@ if [ $1 = "install" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$psNzbDroneUnit" != "-" ] && [ -d /run/systemd/system ]; then
|
if [ "$psNzbDroneUnit" != "-" ] && [ -d /run/systemd/system ]; then
|
||||||
# The user used a systemd auto-startup for NzbDrone, we can deal with that.
|
if [ "$psNzbDroneUnit" = "sonarr.service" ]; then
|
||||||
echo "NzbDrone systemd startup detected at $psNzbDroneUnit, stopping and disabling..."
|
# Conflicts with our new sonarr.service so we have to remove it
|
||||||
deb-systemd-invoke stop $psNzbDroneUnit >/dev/null
|
echo "NzbDrone systemd startup detected at $psNzbDroneUnit, stopping and removing..."
|
||||||
deb-systemd-invoke mask $psNzbDroneUnit >/dev/null
|
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
|
||||||
else
|
else
|
||||||
# We don't support auto migration for other startup methods, so bail.
|
# We don't support auto migration for other startup methods, so bail.
|
||||||
# This leaves the sonarr package in an incomplete state.
|
# This leaves the sonarr package in an incomplete state.
|
||||||
|
|||||||
@@ -1,17 +1,9 @@
|
|||||||
#!/usr/bin/make -f
|
#!/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.
|
# Uncomment this to turn on verbose mode.
|
||||||
#export DH_VERBOSE=1
|
#export DH_VERBOSE=1
|
||||||
|
|
||||||
# Note: System.Native is a dependency of System.Runtime.InteropServices.RuntimeInformation used by SharpRaven,
|
EXCLUDE_MODULEREFS = crypt32 httpapi __Internal
|
||||||
# but SharpRaven doesn't use any functions that need System.Native
|
|
||||||
EXCLUDE_MODULEREFS = crypt32 httpapi System.Native
|
|
||||||
|
|
||||||
%:
|
%:
|
||||||
dh $@ --with=systemd --with=cli
|
dh $@ --with=systemd --with=cli
|
||||||
@@ -20,7 +12,7 @@ EXCLUDE_MODULEREFS = crypt32 httpapi System.Native
|
|||||||
override_dh_installinit:
|
override_dh_installinit:
|
||||||
true
|
true
|
||||||
|
|
||||||
# Sonarr like debug symbols for logging
|
# Sonarr likes debug symbols for logging
|
||||||
override_dh_clistrip:
|
override_dh_clistrip:
|
||||||
|
|
||||||
override_dh_makeclilibs:
|
override_dh_makeclilibs:
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
recommends libcurl3
|
ignores msbuild
|
||||||
ignores msbuild
|
ignores libmediainfo0v5
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
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
|
||||||
|
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
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
|
||||||
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
|
||||||
|
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.8=preview-xenial"
|
||||||
|
|
||||||
|
# Semi-Supported versions
|
||||||
|
MONO_VERSIONS="$MONO_VERSIONS 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.
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
module.exports = [
|
|
||||||
'>0.25%',
|
|
||||||
'not ie 11',
|
|
||||||
'not op_mini all',
|
|
||||||
'not chrome < 60'
|
|
||||||
];
|
|
||||||
+42
-11
@@ -5,14 +5,17 @@ const path = require('path');
|
|||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
const errorHandler = require('./helpers/errorHandler');
|
const errorHandler = require('./helpers/errorHandler');
|
||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
const browsers = require('../browsers');
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
|
|
||||||
const uiFolder = 'UI';
|
const uiFolder = 'UI';
|
||||||
const frontendFolder = path.join(__dirname, '..');
|
const frontendFolder = path.join(__dirname, '..');
|
||||||
const srcFolder = path.join(frontendFolder, 'src');
|
const srcFolder = path.join(frontendFolder, 'src');
|
||||||
const isProduction = process.argv.indexOf('--production') > -1;
|
const isProduction = process.argv.indexOf('--production') > -1;
|
||||||
|
|
||||||
|
const distFolder = path.resolve(frontendFolder, '..', '_output', uiFolder);
|
||||||
|
|
||||||
console.log('Source Folder:', srcFolder);
|
console.log('Source Folder:', srcFolder);
|
||||||
|
console.log('Output Folder:', distFolder);
|
||||||
console.log('isProduction:', isProduction);
|
console.log('isProduction:', isProduction);
|
||||||
|
|
||||||
const cssVarsFiles = [
|
const cssVarsFiles = [
|
||||||
@@ -23,6 +26,22 @@ const cssVarsFiles = [
|
|||||||
'../src/Styles/Variables/zIndexes'
|
'../src/Styles/Variables/zIndexes'
|
||||||
].map(require.resolve);
|
].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 = [
|
const plugins = [
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
__DEV__: !isProduction,
|
__DEV__: !isProduction,
|
||||||
@@ -30,7 +49,12 @@ const plugins = [
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
new MiniCssExtractPlugin({
|
new MiniCssExtractPlugin({
|
||||||
filename: path.join('_output', uiFolder, 'Content', 'styles.css')
|
filename: path.join('Content', 'styles.css')
|
||||||
|
}),
|
||||||
|
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
template: 'frontend/src/index.html',
|
||||||
|
filename: 'index.html'
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -47,8 +71,6 @@ const config = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
entry: {
|
entry: {
|
||||||
preload: 'preload.js',
|
|
||||||
vendor: 'vendor.js',
|
|
||||||
index: 'index.js'
|
index: 'index.js'
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -64,12 +86,20 @@ const config = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
output: {
|
output: {
|
||||||
filename: path.join('_output', uiFolder, '[name].js'),
|
path: distFolder,
|
||||||
|
filename: '[name].js',
|
||||||
sourceMapFilename: '[file].map'
|
sourceMapFilename: '[file].map'
|
||||||
},
|
},
|
||||||
|
|
||||||
optimization: {
|
optimization: {
|
||||||
chunkIds: 'named'
|
chunkIds: 'named',
|
||||||
|
splitChunks: {
|
||||||
|
chunks: 'initial'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
performance: {
|
||||||
|
hints: false
|
||||||
},
|
},
|
||||||
|
|
||||||
plugins,
|
plugins,
|
||||||
@@ -100,7 +130,7 @@ const config = {
|
|||||||
loose: true,
|
loose: true,
|
||||||
debug: false,
|
debug: false,
|
||||||
useBuiltIns: 'entry',
|
useBuiltIns: 'entry',
|
||||||
targets: browsers
|
corejs: 3
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
@@ -119,8 +149,9 @@ const config = {
|
|||||||
loader: 'css-loader',
|
loader: 'css-loader',
|
||||||
options: {
|
options: {
|
||||||
importLoaders: 1,
|
importLoaders: 1,
|
||||||
localIdentName: '[name]/[local]/[hash:base64:5]',
|
modules: {
|
||||||
modules: true
|
localIdentName: '[name]/[local]/[hash:base64:5]'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -184,7 +215,7 @@ const config = {
|
|||||||
|
|
||||||
gulp.task('webpack', () => {
|
gulp.task('webpack', () => {
|
||||||
return webpackStream(config)
|
return webpackStream(config)
|
||||||
.pipe(gulp.dest('./'));
|
.pipe(gulp.dest('_output/UI'));
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('webpackWatch', () => {
|
gulp.task('webpackWatch', () => {
|
||||||
@@ -192,7 +223,7 @@ gulp.task('webpackWatch', () => {
|
|||||||
|
|
||||||
return webpackStream(config)
|
return webpackStream(config)
|
||||||
.on('error', errorHandler)
|
.on('error', errorHandler)
|
||||||
.pipe(gulp.dest('./'))
|
.pipe(gulp.dest('_output/UI'))
|
||||||
.on('error', errorHandler)
|
.on('error', errorHandler)
|
||||||
.pipe(livereload())
|
.pipe(livereload())
|
||||||
.on('error', errorHandler);
|
.on('error', errorHandler);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
const reload = require('require-nocache')(module);
|
const reload = require('require-nocache')(module);
|
||||||
const browsers = require('./browsers');
|
|
||||||
|
|
||||||
module.exports = (ctx, configPath, options) => {
|
module.exports = (ctx, configPath, options) => {
|
||||||
const config = {
|
const config = {
|
||||||
@@ -16,10 +15,7 @@ module.exports = (ctx, configPath, options) => {
|
|||||||
}, {})
|
}, {})
|
||||||
},
|
},
|
||||||
'postcss-color-function': {},
|
'postcss-color-function': {},
|
||||||
'postcss-nested': {},
|
'postcss-nested': {}
|
||||||
autoprefixer: {
|
|
||||||
browsers
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
.message {
|
.messageRemove {
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
|
color: $dangerColor;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,10 +69,14 @@ class RemoveQueueItemModal extends Component {
|
|||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<div className={styles.message}>
|
<div>
|
||||||
Are you sure you want to remove '{sourceTitle}' from the queue?
|
Are you sure you want to remove '{sourceTitle}' from the queue?
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.messageRemove}>
|
||||||
|
Removing will remove the download and the file(s) from the download client.
|
||||||
|
</div>
|
||||||
|
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>Blacklist Release</FormLabel>
|
<FormLabel>Blacklist Release</FormLabel>
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
|
|||||||
@@ -35,14 +35,20 @@
|
|||||||
.message {
|
.message {
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: $largeFontSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
.helpText {
|
.helpText {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
font-weight: 300;
|
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.noSeriesText {
|
||||||
|
margin-top: 80px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.noResults {
|
.noResults {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { icons } from 'Helpers/Props';
|
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||||
|
import { icons, kinds } from 'Helpers/Props';
|
||||||
import Button from 'Components/Link/Button';
|
import Button from 'Components/Link/Button';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
@@ -78,7 +79,8 @@ class AddNewSeries extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
error,
|
error,
|
||||||
items
|
items,
|
||||||
|
hasExistingSeries
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const term = this.state.term;
|
const term = this.state.term;
|
||||||
@@ -121,8 +123,13 @@ class AddNewSeries extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!isFetching && !!error &&
|
!isFetching && !!error ?
|
||||||
<div>Failed to load search results, please try again.</div>
|
<div className={styles.message}>
|
||||||
|
<div className={styles.helpText}>
|
||||||
|
Failed to load search results, please try again.
|
||||||
|
</div>
|
||||||
|
<div>{getErrorMessage(error)}</div>
|
||||||
|
</div> : null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -155,13 +162,34 @@ class AddNewSeries extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!term &&
|
term ?
|
||||||
|
null :
|
||||||
<div className={styles.message}>
|
<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>You can also search using TVDB ID of a show. eg. tvdb:71663</div>
|
||||||
</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 />
|
<div />
|
||||||
</PageContentBodyConnector>
|
</PageContentBodyConnector>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
@@ -176,6 +204,7 @@ AddNewSeries.propTypes = {
|
|||||||
isAdding: PropTypes.bool.isRequired,
|
isAdding: PropTypes.bool.isRequired,
|
||||||
addError: PropTypes.object,
|
addError: PropTypes.object,
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
hasExistingSeries: PropTypes.bool.isRequired,
|
||||||
onSeriesLookupChange: PropTypes.func.isRequired,
|
onSeriesLookupChange: PropTypes.func.isRequired,
|
||||||
onClearSeriesLookup: PropTypes.func.isRequired
|
onClearSeriesLookup: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,13 +10,15 @@ import AddNewSeries from './AddNewSeries';
|
|||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.addSeries,
|
(state) => state.addSeries,
|
||||||
|
(state) => state.series.items.length,
|
||||||
(state) => state.router.location,
|
(state) => state.router.location,
|
||||||
(addSeries, location) => {
|
(addSeries, existingSeriesCount, location) => {
|
||||||
const { params } = parseUrl(location.search);
|
const { params } = parseUrl(location.search);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
...addSeries,
|
||||||
term: params.term,
|
term: params.term,
|
||||||
...addSeries
|
hasExistingSeries: existingSeriesCount > 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -66,9 +66,11 @@ class AddNewSeriesModalContent extends Component {
|
|||||||
languageProfileId,
|
languageProfileId,
|
||||||
seriesType,
|
seriesType,
|
||||||
seasonFolder,
|
seasonFolder,
|
||||||
|
folder,
|
||||||
tags,
|
tags,
|
||||||
showLanguageProfile,
|
showLanguageProfile,
|
||||||
isSmallScreen,
|
isSmallScreen,
|
||||||
|
isWindows,
|
||||||
onModalClose,
|
onModalClose,
|
||||||
onInputChange,
|
onInputChange,
|
||||||
...otherProps
|
...otherProps
|
||||||
@@ -115,6 +117,15 @@ class AddNewSeriesModalContent extends Component {
|
|||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.ROOT_FOLDER_SELECT}
|
type={inputTypes.ROOT_FOLDER_SELECT}
|
||||||
name="rootFolderPath"
|
name="rootFolderPath"
|
||||||
|
valueOptions={{
|
||||||
|
seriesFolder: folder,
|
||||||
|
isWindows
|
||||||
|
}}
|
||||||
|
selectedValueOptions={{
|
||||||
|
seriesFolder: folder,
|
||||||
|
isWindows
|
||||||
|
}}
|
||||||
|
helpText={`'${folder}' subfolder will be created automatically`}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...rootFolderPath}
|
{...rootFolderPath}
|
||||||
/>
|
/>
|
||||||
@@ -260,9 +271,11 @@ AddNewSeriesModalContent.propTypes = {
|
|||||||
languageProfileId: PropTypes.object,
|
languageProfileId: PropTypes.object,
|
||||||
seriesType: PropTypes.object.isRequired,
|
seriesType: PropTypes.object.isRequired,
|
||||||
seasonFolder: PropTypes.object.isRequired,
|
seasonFolder: PropTypes.object.isRequired,
|
||||||
|
folder: PropTypes.string.isRequired,
|
||||||
tags: PropTypes.object.isRequired,
|
tags: PropTypes.object.isRequired,
|
||||||
showLanguageProfile: PropTypes.bool.isRequired,
|
showLanguageProfile: PropTypes.bool.isRequired,
|
||||||
isSmallScreen: PropTypes.bool.isRequired,
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
|
isWindows: PropTypes.bool.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired,
|
onModalClose: PropTypes.func.isRequired,
|
||||||
onInputChange: PropTypes.func.isRequired,
|
onInputChange: PropTypes.func.isRequired,
|
||||||
onAddSeriesPress: PropTypes.func.isRequired
|
onAddSeriesPress: PropTypes.func.isRequired
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { connect } from 'react-redux';
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { setAddSeriesDefault, addSeries } from 'Store/Actions/addSeriesActions';
|
import { setAddSeriesDefault, addSeries } from 'Store/Actions/addSeriesActions';
|
||||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||||
|
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
|
||||||
import selectSettings from 'Store/Selectors/selectSettings';
|
import selectSettings from 'Store/Selectors/selectSettings';
|
||||||
import AddNewSeriesModalContent from './AddNewSeriesModalContent';
|
import AddNewSeriesModalContent from './AddNewSeriesModalContent';
|
||||||
|
|
||||||
@@ -12,7 +13,8 @@ function createMapStateToProps() {
|
|||||||
(state) => state.addSeries,
|
(state) => state.addSeries,
|
||||||
(state) => state.settings.languageProfiles,
|
(state) => state.settings.languageProfiles,
|
||||||
createDimensionsSelector(),
|
createDimensionsSelector(),
|
||||||
(addSeriesState, languageProfiles, dimensions) => {
|
createSystemStatusSelector(),
|
||||||
|
(addSeriesState, languageProfiles, dimensions, systemStatus) => {
|
||||||
const {
|
const {
|
||||||
isAdding,
|
isAdding,
|
||||||
addError,
|
addError,
|
||||||
@@ -32,6 +34,7 @@ function createMapStateToProps() {
|
|||||||
isSmallScreen: dimensions.isSmallScreen,
|
isSmallScreen: dimensions.isSmallScreen,
|
||||||
validationErrors,
|
validationErrors,
|
||||||
validationWarnings,
|
validationWarnings,
|
||||||
|
isWindows: systemStatus.isWindows,
|
||||||
...settings
|
...settings
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
.searchResult {
|
.searchResult {
|
||||||
display: flex;
|
position: relative;
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: $white;
|
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.underlay {
|
||||||
|
@add-mixin cover;
|
||||||
|
|
||||||
|
background-color: $white;
|
||||||
transition: background 500ms;
|
transition: background 500ms;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@@ -14,13 +19,25 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
@add-mixin linkOverlay;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
.poster {
|
.poster {
|
||||||
flex: 0 0 170px;
|
flex: 0 0 170px;
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
height: 250px;
|
height: 250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
flex: 0 1 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
display: flex;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-size: 36px;
|
font-size: 36px;
|
||||||
}
|
}
|
||||||
@@ -30,6 +47,18 @@
|
|||||||
color: $disabledColor;
|
color: $disabledColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tvdbLink {
|
||||||
|
composes: link from '~Components/Link/Link.css';
|
||||||
|
|
||||||
|
margin-top: -4px;
|
||||||
|
margin-left: auto;
|
||||||
|
color: $textColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tvdbLinkIcon {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.alreadyExistsIcon {
|
.alreadyExistsIcon {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
color: #37bc9b;
|
color: #37bc9b;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class AddNewSeriesSearchResult extends Component {
|
|||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (!prevProps.isExistingSeries && this.props.isExistingSeries) {
|
if (!prevProps.isExistingSeries && this.props.isExistingSeries) {
|
||||||
this.onAddSerisModalClose();
|
this.onAddSeriesModalClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,10 +35,14 @@ class AddNewSeriesSearchResult extends Component {
|
|||||||
this.setState({ isNewAddSeriesModalOpen: true });
|
this.setState({ isNewAddSeriesModalOpen: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
onAddSerisModalClose = () => {
|
onAddSeriesModalClose = () => {
|
||||||
this.setState({ isNewAddSeriesModalOpen: false });
|
this.setState({ isNewAddSeriesModalOpen: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onTVDBLinkPress = (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
@@ -53,6 +57,7 @@ class AddNewSeriesSearchResult extends Component {
|
|||||||
overview,
|
overview,
|
||||||
statistics,
|
statistics,
|
||||||
ratings,
|
ratings,
|
||||||
|
folder,
|
||||||
images,
|
images,
|
||||||
isExistingSeries,
|
isExistingSeries,
|
||||||
isSmallScreen
|
isSmallScreen
|
||||||
@@ -72,11 +77,13 @@ class AddNewSeriesSearchResult extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={styles.searchResult}>
|
||||||
<Link
|
<Link
|
||||||
className={styles.searchResult}
|
className={styles.underlay}
|
||||||
{...linkProps}
|
{...linkProps}
|
||||||
>
|
/>
|
||||||
|
|
||||||
|
<div className={styles.overlay}>
|
||||||
{
|
{
|
||||||
isSmallScreen ?
|
isSmallScreen ?
|
||||||
null :
|
null :
|
||||||
@@ -88,7 +95,7 @@ class AddNewSeriesSearchResult extends Component {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div>
|
<div className={styles.content}>
|
||||||
<div className={styles.title}>
|
<div className={styles.title}>
|
||||||
{title}
|
{title}
|
||||||
|
|
||||||
@@ -110,6 +117,18 @@ class AddNewSeriesSearchResult extends Component {
|
|||||||
/> :
|
/> :
|
||||||
null
|
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>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -152,7 +171,7 @@ class AddNewSeriesSearchResult extends Component {
|
|||||||
{overview}
|
{overview}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</div>
|
||||||
|
|
||||||
<AddNewSeriesModal
|
<AddNewSeriesModal
|
||||||
isOpen={isNewAddSeriesModalOpen && !isExistingSeries}
|
isOpen={isNewAddSeriesModalOpen && !isExistingSeries}
|
||||||
@@ -160,8 +179,9 @@ class AddNewSeriesSearchResult extends Component {
|
|||||||
title={title}
|
title={title}
|
||||||
year={year}
|
year={year}
|
||||||
overview={overview}
|
overview={overview}
|
||||||
|
folder={folder}
|
||||||
images={images}
|
images={images}
|
||||||
onModalClose={this.onAddSerisModalClose}
|
onModalClose={this.onAddSeriesModalClose}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -178,6 +198,7 @@ AddNewSeriesSearchResult.propTypes = {
|
|||||||
overview: PropTypes.string,
|
overview: PropTypes.string,
|
||||||
statistics: PropTypes.object.isRequired,
|
statistics: PropTypes.object.isRequired,
|
||||||
ratings: PropTypes.object.isRequired,
|
ratings: PropTypes.object.isRequired,
|
||||||
|
folder: PropTypes.string.isRequired,
|
||||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
isExistingSeries: PropTypes.bool.isRequired,
|
isExistingSeries: PropTypes.bool.isRequired,
|
||||||
isSmallScreen: PropTypes.bool.isRequired
|
isSmallScreen: PropTypes.bool.isRequired
|
||||||
|
|||||||
@@ -75,10 +75,10 @@ class ImportSeriesSelectFolder extends Component {
|
|||||||
Some tips to ensure the import goes smoothly:
|
Some tips to ensure the import goes smoothly:
|
||||||
<ul>
|
<ul>
|
||||||
<li className={styles.tip}>
|
<li className={styles.tip}>
|
||||||
Make sure your files include the quality in the name. eg. <span className={styles.code}>episode.s02e15.bluray.mkv</span>
|
Make sure that your files include the quality in their filenames. eg. <span className={styles.code}>episode.s02e15.bluray.mkv</span>
|
||||||
</li>
|
</li>
|
||||||
<li className={styles.tip}>
|
<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>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
flex: 1 0 14.28%;
|
flex: 1 0 14.28%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-height: 70px;
|
min-height: 70px;
|
||||||
border-bottom: 1px solid $borderColor;
|
border-bottom: 1px solid $calendarBorderColor;
|
||||||
border-left: 1px solid $borderColor;
|
border-left: 1px solid $calendarBorderColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
.isSingleDay {
|
.isSingleDay {
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
.dayOfMonth {
|
.dayOfMonth {
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
border-bottom: 1px solid $borderColor;
|
border-bottom: 1px solid $calendarBorderColor;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.days {
|
.days {
|
||||||
display: flex;
|
display: flex;
|
||||||
border-right: 1px solid $borderColor;
|
border-right: 1px solid $calendarBorderColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
.day,
|
.day,
|
||||||
|
|||||||
@@ -16,10 +16,13 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.episodeInfo {
|
||||||
|
color: $calendarTextDim;
|
||||||
|
}
|
||||||
|
|
||||||
.seriesTitle,
|
.seriesTitle,
|
||||||
.episodeTitle {
|
.episodeTitle {
|
||||||
@add-mixin truncate;
|
@add-mixin truncate;
|
||||||
|
|
||||||
flex: 1 0 1px;
|
flex: 1 0 1px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
@@ -37,6 +40,10 @@
|
|||||||
margin-left: 3px;
|
margin-left: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.airTime {
|
||||||
|
color: $calendarTextDim;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Status
|
* Status
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ class CalendarEvent extends Component {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div>
|
<div className={styles.airTime}>
|
||||||
{formatTime(airDateUtc, timeFormat)} - {formatTime(endTime.toISOString(), timeFormat, { includeMinuteZero: true })}
|
{formatTime(airDateUtc, timeFormat)} - {formatTime(endTime.toISOString(), timeFormat, { includeMinuteZero: true })}
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
|
|
||||||
.seriesTitle {
|
.seriesTitle {
|
||||||
@add-mixin truncate;
|
@add-mixin truncate;
|
||||||
|
|
||||||
flex: 1 0 1px;
|
flex: 1 0 1px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
color: #3a3f51;
|
color: #3a3f51;
|
||||||
@@ -23,10 +22,12 @@
|
|||||||
|
|
||||||
.airTime {
|
.airTime {
|
||||||
flex: 1 0 1px;
|
flex: 1 0 1px;
|
||||||
|
color: $calendarTextDim;
|
||||||
}
|
}
|
||||||
|
|
||||||
.episodeInfo {
|
.episodeInfo {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
|
color: $calendarTextDim;
|
||||||
}
|
}
|
||||||
|
|
||||||
.absoluteEpisodeNumber {
|
.absoluteEpisodeNumber {
|
||||||
@@ -80,3 +81,7 @@
|
|||||||
.premiere {
|
.premiere {
|
||||||
composes: premiere from '~Calendar/Events/CalendarEvent.css';
|
composes: premiere from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.unaired {
|
||||||
|
composes: unaired from '~Calendar/Events/CalendarEvent.css';
|
||||||
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class ErrorBoundary extends Component {
|
|||||||
|
|
||||||
ErrorBoundary.propTypes = {
|
ErrorBoundary.propTypes = {
|
||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.node.isRequired,
|
||||||
errorComponent: PropTypes.func.isRequired
|
errorComponent: PropTypes.elementType.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ErrorBoundary;
|
export default ErrorBoundary;
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ class FileBrowserModalContent extends Component {
|
|||||||
<Scroller
|
<Scroller
|
||||||
ref={this.setScrollerRef}
|
ref={this.setScrollerRef}
|
||||||
className={styles.scroller}
|
className={styles.scroller}
|
||||||
|
scrollDirection={scrollDirections.BOTH}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
!!error &&
|
!!error &&
|
||||||
@@ -152,7 +153,10 @@ class FileBrowserModalContent extends Component {
|
|||||||
|
|
||||||
{
|
{
|
||||||
isPopulated && !error &&
|
isPopulated && !error &&
|
||||||
<Table columns={columns}>
|
<Table
|
||||||
|
horizontalScroll={false}
|
||||||
|
columns={columns}
|
||||||
|
>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{
|
{
|
||||||
emptyParent &&
|
emptyParent &&
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ class FilterBuilderModalContent extends Component {
|
|||||||
filterBuilderProps,
|
filterBuilderProps,
|
||||||
isSaving,
|
isSaving,
|
||||||
saveError,
|
saveError,
|
||||||
|
onCancelPress,
|
||||||
onModalClose
|
onModalClose
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@@ -190,7 +191,7 @@ class FilterBuilderModalContent extends Component {
|
|||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button onPress={onModalClose}>
|
<Button onPress={onCancelPress}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
@@ -220,6 +221,7 @@ FilterBuilderModalContent.propTypes = {
|
|||||||
dispatchDeleteCustomFilter: PropTypes.func.isRequired,
|
dispatchDeleteCustomFilter: PropTypes.func.isRequired,
|
||||||
onSaveCustomFilterPress: PropTypes.func.isRequired,
|
onSaveCustomFilterPress: PropTypes.func.isRequired,
|
||||||
dispatchSetFilter: PropTypes.func.isRequired,
|
dispatchSetFilter: PropTypes.func.isRequired,
|
||||||
|
onCancelPress: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,21 @@ import { connect } from 'react-redux';
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import sortByName from 'Utilities/Array/sortByName';
|
import sortByName from 'Utilities/Array/sortByName';
|
||||||
import { filterBuilderTypes } from 'Helpers/Props';
|
import { filterBuilderTypes } from 'Helpers/Props';
|
||||||
|
import * as filterTypes from 'Helpers/Props/filterTypes';
|
||||||
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
||||||
|
|
||||||
function createTagListSelector() {
|
function createTagListSelector() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
|
(state, { filterType }) => filterType,
|
||||||
(state, { sectionItems }) => sectionItems,
|
(state, { sectionItems }) => sectionItems,
|
||||||
(state, { selectedFilterBuilderProp }) => selectedFilterBuilderProp,
|
(state, { selectedFilterBuilderProp }) => selectedFilterBuilderProp,
|
||||||
(sectionItems, selectedFilterBuilderProp) => {
|
(filterType, sectionItems, selectedFilterBuilderProp) => {
|
||||||
if (
|
if (
|
||||||
selectedFilterBuilderProp.type === filterBuilderTypes.NUMBER ||
|
(selectedFilterBuilderProp.type === filterBuilderTypes.NUMBER ||
|
||||||
selectedFilterBuilderProp.type === filterBuilderTypes.STRING
|
selectedFilterBuilderProp.type === filterBuilderTypes.STRING) &&
|
||||||
|
filterType !== filterTypes.EQUAL &&
|
||||||
|
filterType !== filterBuilderTypes.NOT_EQUAL ||
|
||||||
|
!selectedFilterBuilderProp.optionsSelector
|
||||||
) {
|
) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
.tag {
|
.tag {
|
||||||
|
height: 21px;
|
||||||
|
|
||||||
&.isLastTag {
|
&.isLastTag {
|
||||||
.or {
|
.or {
|
||||||
display: none;
|
display: none;
|
||||||
|
|||||||
@@ -29,10 +29,10 @@ function CustomFiltersModalContent(props) {
|
|||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
{
|
{
|
||||||
customFilters.map((customFilter, index) => {
|
customFilters.map((customFilter) => {
|
||||||
return (
|
return (
|
||||||
<CustomFilter
|
<CustomFilter
|
||||||
key={index}
|
key={customFilter.id}
|
||||||
id={customFilter.id}
|
id={customFilter.id}
|
||||||
label={customFilter.label}
|
label={customFilter.label}
|
||||||
filters={customFilter.filters}
|
filters={customFilter.filters}
|
||||||
|
|||||||
@@ -34,6 +34,17 @@ class FilterModal extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCancelPress = () => {
|
||||||
|
if (this.state.filterBuilder) {
|
||||||
|
this.setState({
|
||||||
|
filterBuilder: false,
|
||||||
|
id: null
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.onModalClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onModalClose = () => {
|
onModalClose = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
filterBuilder: false,
|
filterBuilder: false,
|
||||||
@@ -67,6 +78,7 @@ class FilterModal extends Component {
|
|||||||
<FilterBuilderModalContentConnector
|
<FilterBuilderModalContentConnector
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
id={id}
|
id={id}
|
||||||
|
onCancelPress={this.onCancelPress}
|
||||||
onModalClose={this.onModalClose}
|
onModalClose={this.onModalClose}
|
||||||
/> :
|
/> :
|
||||||
<CustomFiltersModalContentConnector
|
<CustomFiltersModalContentConnector
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
.suggestionsList {
|
.suggestionsList {
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
|
max-height: 200px;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ class AutoSuggestInput extends Component {
|
|||||||
className: classNames(
|
className: classNames(
|
||||||
className,
|
className,
|
||||||
hasError && styles.hasError,
|
hasError && styles.hasError,
|
||||||
hasWarning && styles.hasWarning,
|
hasWarning && styles.hasWarning
|
||||||
),
|
),
|
||||||
name,
|
name,
|
||||||
value,
|
value,
|
||||||
@@ -234,7 +234,7 @@ AutoSuggestInput.propTypes = {
|
|||||||
minHeight: PropTypes.number.isRequired,
|
minHeight: PropTypes.number.isRequired,
|
||||||
maxHeight: PropTypes.number.isRequired,
|
maxHeight: PropTypes.number.isRequired,
|
||||||
getSuggestionValue: PropTypes.func.isRequired,
|
getSuggestionValue: PropTypes.func.isRequired,
|
||||||
renderInputComponent: PropTypes.func,
|
renderInputComponent: PropTypes.elementType,
|
||||||
renderSuggestion: PropTypes.func.isRequired,
|
renderSuggestion: PropTypes.func.isRequired,
|
||||||
onInputChange: PropTypes.func,
|
onInputChange: PropTypes.func,
|
||||||
onInputKeyDown: PropTypes.func,
|
onInputKeyDown: PropTypes.func,
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import PropTypes from 'prop-types';
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { fetchDevices, clearDevices } from 'Store/Actions/deviceActions';
|
import { fetchOptions, clearOptions } from 'Store/Actions/providerOptionActions';
|
||||||
import DeviceInput from './DeviceInput';
|
import DeviceInput from './DeviceInput';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state, { value }) => value,
|
(state, { value }) => value,
|
||||||
(state) => state.devices,
|
(state) => state.providerOptions,
|
||||||
(value, devices) => {
|
(value, devices) => {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -37,8 +37,8 @@ function createMapStateToProps() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
dispatchFetchDevices: fetchDevices,
|
dispatchFetchOptions: fetchOptions,
|
||||||
dispatchClearDevices: clearDevices
|
dispatchClearOptions: clearOptions
|
||||||
};
|
};
|
||||||
|
|
||||||
class DeviceInputConnector extends Component {
|
class DeviceInputConnector extends Component {
|
||||||
@@ -51,7 +51,7 @@ class DeviceInputConnector extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount = () => {
|
componentWillUnmount = () => {
|
||||||
// this.props.dispatchClearDevices();
|
this.props.dispatchClearOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -61,10 +61,14 @@ class DeviceInputConnector extends Component {
|
|||||||
const {
|
const {
|
||||||
provider,
|
provider,
|
||||||
providerData,
|
providerData,
|
||||||
dispatchFetchDevices
|
dispatchFetchOptions
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
dispatchFetchDevices({ provider, providerData });
|
dispatchFetchOptions({
|
||||||
|
action: 'getDevices',
|
||||||
|
provider,
|
||||||
|
providerData
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -92,8 +96,8 @@ DeviceInputConnector.propTypes = {
|
|||||||
providerData: PropTypes.object.isRequired,
|
providerData: PropTypes.object.isRequired,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
dispatchFetchDevices: PropTypes.func.isRequired,
|
dispatchFetchOptions: PropTypes.func.isRequired,
|
||||||
dispatchClearDevices: PropTypes.func.isRequired
|
dispatchClearOptions: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps)(DeviceInputConnector);
|
export default connect(createMapStateToProps, mapDispatchToProps)(DeviceInputConnector);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import React, { Component } from 'react';
|
|||||||
import { Manager, Popper, Reference } from 'react-popper';
|
import { Manager, Popper, Reference } from 'react-popper';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import getUniqueElememtId from 'Utilities/getUniqueElementId';
|
import getUniqueElememtId from 'Utilities/getUniqueElementId';
|
||||||
import isMobileUtil from 'Utilities/isMobile';
|
import { isMobile as isMobileUtil } from 'Utilities/mobile';
|
||||||
import * as keyCodes from 'Utilities/Constants/keyCodes';
|
import * as keyCodes from 'Utilities/Constants/keyCodes';
|
||||||
import { icons, sizes, scrollDirections } from 'Helpers/Props';
|
import { icons, sizes, scrollDirections } from 'Helpers/Props';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
@@ -262,6 +262,7 @@ class EnhancedSelectInput extends Component {
|
|||||||
isDisabled,
|
isDisabled,
|
||||||
hasError,
|
hasError,
|
||||||
hasWarning,
|
hasWarning,
|
||||||
|
valueOptions,
|
||||||
selectedValueOptions,
|
selectedValueOptions,
|
||||||
selectedValueComponent: SelectedValueComponent,
|
selectedValueComponent: SelectedValueComponent,
|
||||||
optionComponent: OptionComponent
|
optionComponent: OptionComponent
|
||||||
@@ -363,6 +364,7 @@ class EnhancedSelectInput extends Component {
|
|||||||
key={v.key}
|
key={v.key}
|
||||||
id={v.key}
|
id={v.key}
|
||||||
isSelected={index === selectedIndex}
|
isSelected={index === selectedIndex}
|
||||||
|
{...valueOptions}
|
||||||
{...v}
|
{...v}
|
||||||
isMobile={false}
|
isMobile={false}
|
||||||
onSelect={this.onSelect}
|
onSelect={this.onSelect}
|
||||||
@@ -404,6 +406,7 @@ class EnhancedSelectInput extends Component {
|
|||||||
key={v.key}
|
key={v.key}
|
||||||
id={v.key}
|
id={v.key}
|
||||||
isSelected={index === selectedIndex}
|
isSelected={index === selectedIndex}
|
||||||
|
{...valueOptions}
|
||||||
{...v}
|
{...v}
|
||||||
isMobile={true}
|
isMobile={true}
|
||||||
onSelect={this.onSelect}
|
onSelect={this.onSelect}
|
||||||
@@ -431,9 +434,10 @@ EnhancedSelectInput.propTypes = {
|
|||||||
isDisabled: PropTypes.bool,
|
isDisabled: PropTypes.bool,
|
||||||
hasError: PropTypes.bool,
|
hasError: PropTypes.bool,
|
||||||
hasWarning: PropTypes.bool,
|
hasWarning: PropTypes.bool,
|
||||||
|
valueOptions: PropTypes.object.isRequired,
|
||||||
selectedValueOptions: PropTypes.object.isRequired,
|
selectedValueOptions: PropTypes.object.isRequired,
|
||||||
selectedValueComponent: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
|
selectedValueComponent: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
|
||||||
optionComponent: PropTypes.func,
|
optionComponent: PropTypes.elementType,
|
||||||
onChange: PropTypes.func.isRequired
|
onChange: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -441,6 +445,7 @@ EnhancedSelectInput.defaultProps = {
|
|||||||
className: styles.enhancedSelect,
|
className: styles.enhancedSelect,
|
||||||
disabledClassName: styles.isDisabled,
|
disabledClassName: styles.isDisabled,
|
||||||
isDisabled: false,
|
isDisabled: false,
|
||||||
|
valueOptions: {},
|
||||||
selectedValueOptions: {},
|
selectedValueOptions: {},
|
||||||
selectedValueComponent: HintedSelectInputSelectedValue,
|
selectedValueComponent: HintedSelectInputSelectedValue,
|
||||||
optionComponent: HintedSelectInputOption
|
optionComponent: HintedSelectInputOption
|
||||||
|
|||||||
@@ -18,10 +18,19 @@ class PathInput extends Component {
|
|||||||
this._node = document.getElementById('portal-root');
|
this._node = document.getElementById('portal-root');
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
value: props.value,
|
||||||
isFileBrowserModalOpen: false
|
isFileBrowserModalOpen: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
const { value } = this.props;
|
||||||
|
|
||||||
|
if (prevProps.value !== value) {
|
||||||
|
this.setState({ value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Control
|
// Control
|
||||||
|
|
||||||
@@ -51,11 +60,8 @@ class PathInput extends Component {
|
|||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onInputChange = (event, { newValue }) => {
|
onInputChange = ({ value }) => {
|
||||||
this.props.onChange({
|
this.setState({ value });
|
||||||
name: this.props.name,
|
|
||||||
value: newValue
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onInputKeyDown = (event) => {
|
onInputKeyDown = (event) => {
|
||||||
@@ -77,6 +83,11 @@ class PathInput extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onInputBlur = () => {
|
onInputBlur = () => {
|
||||||
|
this.props.onChange({
|
||||||
|
name: this.props.name,
|
||||||
|
value: this.state.value
|
||||||
|
});
|
||||||
|
|
||||||
this.props.onClearPaths();
|
this.props.onClearPaths();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,13 +119,18 @@ class PathInput extends Component {
|
|||||||
const {
|
const {
|
||||||
className,
|
className,
|
||||||
name,
|
name,
|
||||||
value,
|
|
||||||
paths,
|
paths,
|
||||||
includeFiles,
|
includeFiles,
|
||||||
hasFileBrowser,
|
hasFileBrowser,
|
||||||
onChange,
|
onChange,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
value,
|
||||||
|
isFileBrowserModalOpen
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<AutoSuggestInput
|
<AutoSuggestInput
|
||||||
@@ -130,7 +146,7 @@ class PathInput extends Component {
|
|||||||
onSuggestionSelected={this.onSuggestionSelected}
|
onSuggestionSelected={this.onSuggestionSelected}
|
||||||
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
|
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
|
||||||
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
||||||
onChange={onChange}
|
onChange={this.onInputChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -144,7 +160,7 @@ class PathInput extends Component {
|
|||||||
</FormInputButton>
|
</FormInputButton>
|
||||||
|
|
||||||
<FileBrowserModal
|
<FileBrowserModal
|
||||||
isOpen={this.state.isFileBrowserModalOpen}
|
isOpen={isFileBrowserModalOpen}
|
||||||
name={name}
|
name={name}
|
||||||
value={value}
|
value={value}
|
||||||
includeFiles={includeFiles}
|
includeFiles={includeFiles}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ function getType(type) {
|
|||||||
return inputTypes.NUMBER;
|
return inputTypes.NUMBER;
|
||||||
case 'path':
|
case 'path':
|
||||||
return inputTypes.PATH;
|
return inputTypes.PATH;
|
||||||
case 'filepath':
|
case 'filePath':
|
||||||
return inputTypes.PATH;
|
return inputTypes.PATH;
|
||||||
case 'select':
|
case 'select':
|
||||||
return inputTypes.SELECT;
|
return inputTypes.SELECT;
|
||||||
@@ -28,7 +28,7 @@ function getType(type) {
|
|||||||
return inputTypes.TEXT_TAG;
|
return inputTypes.TEXT_TAG;
|
||||||
case 'textbox':
|
case 'textbox':
|
||||||
return inputTypes.TEXT;
|
return inputTypes.TEXT;
|
||||||
case 'oauth':
|
case 'oAuth':
|
||||||
return inputTypes.OAUTH;
|
return inputTypes.OAUTH;
|
||||||
default:
|
default:
|
||||||
return inputTypes.TEXT;
|
return inputTypes.TEXT;
|
||||||
@@ -60,6 +60,7 @@ function ProviderFieldFormGroup(props) {
|
|||||||
value,
|
value,
|
||||||
type,
|
type,
|
||||||
advanced,
|
advanced,
|
||||||
|
hidden,
|
||||||
pending,
|
pending,
|
||||||
errors,
|
errors,
|
||||||
warnings,
|
warnings,
|
||||||
@@ -68,6 +69,13 @@ function ProviderFieldFormGroup(props) {
|
|||||||
...otherProps
|
...otherProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
if (
|
||||||
|
hidden === 'hidden' ||
|
||||||
|
(hidden === 'hiddenIfNotSet' && !value)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
advancedSettings={advancedSettings}
|
advancedSettings={advancedSettings}
|
||||||
@@ -86,7 +94,7 @@ function ProviderFieldFormGroup(props) {
|
|||||||
errors={errors}
|
errors={errors}
|
||||||
warnings={warnings}
|
warnings={warnings}
|
||||||
pending={pending}
|
pending={pending}
|
||||||
includeFiles={type === 'filepath' ? true : undefined}
|
includeFiles={type === 'filePath' ? true : undefined}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
@@ -108,6 +116,7 @@ ProviderFieldFormGroup.propTypes = {
|
|||||||
value: PropTypes.any,
|
value: PropTypes.any,
|
||||||
type: PropTypes.string.isRequired,
|
type: PropTypes.string.isRequired,
|
||||||
advanced: PropTypes.bool.isRequired,
|
advanced: PropTypes.bool.isRequired,
|
||||||
|
hidden: PropTypes.string,
|
||||||
pending: PropTypes.bool.isRequired,
|
pending: PropTypes.bool.isRequired,
|
||||||
errors: PropTypes.arrayOf(PropTypes.object).isRequired,
|
errors: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
warnings: PropTypes.arrayOf(PropTypes.object).isRequired,
|
warnings: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
@@ -13,7 +12,7 @@ function createMapStateToProps() {
|
|||||||
(state) => state.rootFolders,
|
(state) => state.rootFolders,
|
||||||
(state, { includeNoChange }) => includeNoChange,
|
(state, { includeNoChange }) => includeNoChange,
|
||||||
(rootFolders, includeNoChange) => {
|
(rootFolders, includeNoChange) => {
|
||||||
const values = _.map(rootFolders.items, (rootFolder) => {
|
const values = rootFolders.items.map((rootFolder) => {
|
||||||
return {
|
return {
|
||||||
key: rootFolder.path,
|
key: rootFolder.path,
|
||||||
value: rootFolder.path,
|
value: rootFolder.path,
|
||||||
@@ -85,7 +84,7 @@ class RootFolderSelectInputConnector extends Component {
|
|||||||
onChange
|
onChange
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (!value || !_.some(values, (v) => v.key === value) || value === ADD_NEW_KEY) {
|
if (!value || !values.some((v) => v.key === value) || value === ADD_NEW_KEY) {
|
||||||
const defaultValue = values[0];
|
const defaultValue = values[0];
|
||||||
|
|
||||||
if (defaultValue.key === ADD_NEW_KEY) {
|
if (defaultValue.key === ADD_NEW_KEY) {
|
||||||
@@ -96,6 +95,27 @@ 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
|
// Listeners
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seriesFolder {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
color: $disabledColor;
|
||||||
|
}
|
||||||
|
|
||||||
.freeSpace {
|
.freeSpace {
|
||||||
margin-left: 15px;
|
margin-left: 15px;
|
||||||
color: $darkGray;
|
color: $darkGray;
|
||||||
|
|||||||
@@ -7,14 +7,20 @@ import styles from './RootFolderSelectInputOption.css';
|
|||||||
|
|
||||||
function RootFolderSelectInputOption(props) {
|
function RootFolderSelectInputOption(props) {
|
||||||
const {
|
const {
|
||||||
|
id,
|
||||||
value,
|
value,
|
||||||
freeSpace,
|
freeSpace,
|
||||||
|
seriesFolder,
|
||||||
isMobile,
|
isMobile,
|
||||||
|
isWindows,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const slashCharacter = isWindows ? '\\' : '/';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EnhancedSelectInputOption
|
<EnhancedSelectInputOption
|
||||||
|
id={id}
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
>
|
>
|
||||||
@@ -23,7 +29,18 @@ function RootFolderSelectInputOption(props) {
|
|||||||
isMobile && styles.isMobile
|
isMobile && styles.isMobile
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div>{value}</div>
|
<div className={styles.value}>
|
||||||
|
{value}
|
||||||
|
|
||||||
|
{
|
||||||
|
seriesFolder && id !== 'addNew' ?
|
||||||
|
<div className={styles.seriesFolder}>
|
||||||
|
{slashCharacter}
|
||||||
|
{seriesFolder}
|
||||||
|
</div> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
freeSpace != null &&
|
freeSpace != null &&
|
||||||
@@ -37,9 +54,12 @@ function RootFolderSelectInputOption(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
RootFolderSelectInputOption.propTypes = {
|
RootFolderSelectInputOption.propTypes = {
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
value: PropTypes.string.isRequired,
|
value: PropTypes.string.isRequired,
|
||||||
freeSpace: PropTypes.number,
|
freeSpace: PropTypes.number,
|
||||||
isMobile: PropTypes.bool.isRequired
|
seriesFolder: PropTypes.string,
|
||||||
|
isMobile: PropTypes.bool.isRequired,
|
||||||
|
isWindows: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RootFolderSelectInputOption;
|
export default RootFolderSelectInputOption;
|
||||||
|
|||||||
@@ -7,10 +7,20 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pathContainer {
|
||||||
|
@add-mixin truncate;
|
||||||
|
display: flex;
|
||||||
|
flex: 1 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
.path {
|
.path {
|
||||||
@add-mixin truncate;
|
@add-mixin truncate;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
flex: 1 0 0;
|
.seriesFolder {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
color: $disabledColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
.freeSpace {
|
.freeSpace {
|
||||||
|
|||||||
@@ -8,17 +8,32 @@ function RootFolderSelectInputSelectedValue(props) {
|
|||||||
const {
|
const {
|
||||||
value,
|
value,
|
||||||
freeSpace,
|
freeSpace,
|
||||||
|
seriesFolder,
|
||||||
includeFreeSpace,
|
includeFreeSpace,
|
||||||
|
isWindows,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const slashCharacter = isWindows ? '\\' : '/';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EnhancedSelectInputSelectedValue
|
<EnhancedSelectInputSelectedValue
|
||||||
className={styles.selectedValue}
|
className={styles.selectedValue}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
>
|
>
|
||||||
<div className={styles.path}>
|
<div className={styles.pathContainer}>
|
||||||
{value}
|
<div className={styles.path}>
|
||||||
|
{value}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
seriesFolder ?
|
||||||
|
<div className={styles.seriesFolder}>
|
||||||
|
{slashCharacter}
|
||||||
|
{seriesFolder}
|
||||||
|
</div> :
|
||||||
|
null
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -34,6 +49,8 @@ function RootFolderSelectInputSelectedValue(props) {
|
|||||||
RootFolderSelectInputSelectedValue.propTypes = {
|
RootFolderSelectInputSelectedValue.propTypes = {
|
||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
freeSpace: PropTypes.number,
|
freeSpace: PropTypes.number,
|
||||||
|
seriesFolder: PropTypes.string,
|
||||||
|
isWindows: PropTypes.bool,
|
||||||
includeFreeSpace: PropTypes.bool.isRequired
|
includeFreeSpace: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,6 @@
|
|||||||
min-width: 20%;
|
min-width: 20%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
width: 0%;
|
width: 0%;
|
||||||
height: 21px;
|
height: 31px;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,7 +226,7 @@ class TagInput extends Component {
|
|||||||
className={styles.internalInput}
|
className={styles.internalInput}
|
||||||
inputContainerClassName={classNames(
|
inputContainerClassName={classNames(
|
||||||
inputContainerClassName,
|
inputContainerClassName,
|
||||||
isFocused && styles.isFocused,
|
isFocused && styles.isFocused
|
||||||
)}
|
)}
|
||||||
value={value}
|
value={value}
|
||||||
suggestions={suggestions}
|
suggestions={suggestions}
|
||||||
@@ -260,7 +260,7 @@ TagInput.propTypes = {
|
|||||||
minQueryLength: PropTypes.number.isRequired,
|
minQueryLength: PropTypes.number.isRequired,
|
||||||
hasError: PropTypes.bool,
|
hasError: PropTypes.bool,
|
||||||
hasWarning: PropTypes.bool,
|
hasWarning: PropTypes.bool,
|
||||||
tagComponent: PropTypes.func.isRequired,
|
tagComponent: PropTypes.elementType.isRequired,
|
||||||
onTagAdd: PropTypes.func.isRequired,
|
onTagAdd: PropTypes.func.isRequired,
|
||||||
onTagDelete: PropTypes.func.isRequired
|
onTagDelete: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
bottom: -1px;
|
bottom: -1px;
|
||||||
left: -1px;
|
left: -1px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: start;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
padding: 6px 16px;
|
padding: 1px 16px;
|
||||||
|
min-height: 33px;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ TagInputInput.propTypes = {
|
|||||||
inputProps: PropTypes.object.isRequired,
|
inputProps: PropTypes.object.isRequired,
|
||||||
kind: PropTypes.oneOf(kinds.all).isRequired,
|
kind: PropTypes.oneOf(kinds.all).isRequired,
|
||||||
isFocused: PropTypes.bool.isRequired,
|
isFocused: PropTypes.bool.isRequired,
|
||||||
tagComponent: PropTypes.func.isRequired,
|
tagComponent: PropTypes.elementType.isRequired,
|
||||||
onTagDelete: PropTypes.func.isRequired,
|
onTagDelete: PropTypes.func.isRequired,
|
||||||
onInputContainerPress: PropTypes.func.isRequired
|
onInputContainerPress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
.tag {
|
||||||
|
composes: link from '~Components/Link/Link.css';
|
||||||
|
|
||||||
|
height: 31px;
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { kinds } from 'Helpers/Props';
|
|||||||
import tagShape from 'Helpers/Props/Shapes/tagShape';
|
import tagShape from 'Helpers/Props/Shapes/tagShape';
|
||||||
import Label from 'Components/Label';
|
import Label from 'Components/Label';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
|
import styles from './TagInputTag.css';
|
||||||
|
|
||||||
class TagInputTag extends Component {
|
class TagInputTag extends Component {
|
||||||
|
|
||||||
@@ -31,9 +32,9 @@ class TagInputTag extends Component {
|
|||||||
tag,
|
tag,
|
||||||
kind
|
kind
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
|
className={styles.tag}
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
onPress={this.onDelete}
|
onPress={this.onDelete}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -95,8 +95,8 @@ FilterMenu.propTypes = {
|
|||||||
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||||
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
buttonComponent: PropTypes.func.isRequired,
|
buttonComponent: PropTypes.elementType.isRequired,
|
||||||
filterModalConnectorComponent: PropTypes.func,
|
filterModalConnectorComponent: PropTypes.elementType,
|
||||||
filterModalConnectorComponentProps: PropTypes.object,
|
filterModalConnectorComponentProps: PropTypes.object,
|
||||||
onFilterSelect: PropTypes.func.isRequired
|
onFilterSelect: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ class Menu extends Component {
|
|||||||
|
|
||||||
this._scheduleUpdate = null;
|
this._scheduleUpdate = null;
|
||||||
this._menuButtonId = getUniqueElememtId();
|
this._menuButtonId = getUniqueElememtId();
|
||||||
|
this._menuContentId = getUniqueElememtId();
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isMenuOpen: false,
|
isMenuOpen: false,
|
||||||
@@ -99,12 +100,14 @@ class Menu extends Component {
|
|||||||
window.addEventListener('resize', this.onWindowResize);
|
window.addEventListener('resize', this.onWindowResize);
|
||||||
window.addEventListener('scroll', this.onWindowScroll, { capture: true });
|
window.addEventListener('scroll', this.onWindowScroll, { capture: true });
|
||||||
window.addEventListener('click', this.onWindowClick);
|
window.addEventListener('click', this.onWindowClick);
|
||||||
|
window.addEventListener('touchstart', this.onTouchStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
_removeListener() {
|
_removeListener() {
|
||||||
window.removeEventListener('resize', this.onWindowResize);
|
window.removeEventListener('resize', this.onWindowResize);
|
||||||
window.removeEventListener('scroll', this.onWindowScroll, { capture: true });
|
window.removeEventListener('scroll', this.onWindowScroll, { capture: true });
|
||||||
window.removeEventListener('click', this.onWindowClick);
|
window.removeEventListener('click', this.onWindowClick);
|
||||||
|
window.removeEventListener('touchstart', this.onTouchStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -123,6 +126,30 @@ class Menu extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onTouchStart = (event) => {
|
||||||
|
const menuButton = document.getElementById(this._menuButtonId);
|
||||||
|
const menuContent = document.getElementById(this._menuContentId);
|
||||||
|
|
||||||
|
if (!menuButton || !menuContent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.targetTouches.length !== 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = event.targetTouches[0].target;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!menuButton.contains(target) &&
|
||||||
|
!menuContent.contains(target) &&
|
||||||
|
this.state.isMenuOpen
|
||||||
|
) {
|
||||||
|
this.setState({ isMenuOpen: false });
|
||||||
|
this._removeListener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onWindowResize = () => {
|
onWindowResize = () => {
|
||||||
this.setMaxHeight();
|
this.setMaxHeight();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import getUniqueElementId from 'Utilities/getUniqueElementId';
|
||||||
import Scroller from 'Components/Scroller/Scroller';
|
import Scroller from 'Components/Scroller/Scroller';
|
||||||
import styles from './MenuContent.css';
|
import styles from './MenuContent.css';
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ class MenuContent extends Component {
|
|||||||
const {
|
const {
|
||||||
forwardedRef,
|
forwardedRef,
|
||||||
className,
|
className,
|
||||||
|
id,
|
||||||
children,
|
children,
|
||||||
style,
|
style,
|
||||||
isOpen
|
isOpen
|
||||||
@@ -19,6 +21,7 @@ class MenuContent extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
id={id}
|
||||||
ref={forwardedRef}
|
ref={forwardedRef}
|
||||||
className={className}
|
className={className}
|
||||||
style={style}
|
style={style}
|
||||||
@@ -38,13 +41,15 @@ class MenuContent extends Component {
|
|||||||
MenuContent.propTypes = {
|
MenuContent.propTypes = {
|
||||||
forwardedRef: PropTypes.func,
|
forwardedRef: PropTypes.func,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.node.isRequired,
|
||||||
style: PropTypes.object,
|
style: PropTypes.object,
|
||||||
isOpen: PropTypes.bool
|
isOpen: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuContent.defaultProps = {
|
MenuContent.defaultProps = {
|
||||||
className: styles.menuContent
|
className: styles.menuContent,
|
||||||
|
id: getUniqueElementId()
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MenuContent;
|
export default MenuContent;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
.menuItem {
|
.menuItem {
|
||||||
@add-mixin truncate;
|
@add-mixin truncate;
|
||||||
|
|
||||||
display: block;
|
display: block;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
@@ -17,3 +16,8 @@
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.isDisabled {
|
||||||
|
color: $disabledColor;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
import styles from './MenuItem.css';
|
import styles from './MenuItem.css';
|
||||||
|
|
||||||
@@ -12,12 +13,17 @@ class MenuItem extends Component {
|
|||||||
const {
|
const {
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
|
isDisabled,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
className={className}
|
className={classNames(
|
||||||
|
className,
|
||||||
|
isDisabled && styles.isDisabled
|
||||||
|
)}
|
||||||
|
isDisabled={isDisabled}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@@ -28,11 +34,13 @@ class MenuItem extends Component {
|
|||||||
|
|
||||||
MenuItem.propTypes = {
|
MenuItem.propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
children: PropTypes.node.isRequired
|
children: PropTypes.node.isRequired,
|
||||||
|
isDisabled: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuItem.defaultProps = {
|
MenuItem.defaultProps = {
|
||||||
className: styles.menuItem
|
className: styles.menuItem,
|
||||||
|
isDisabled: false
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MenuItem;
|
export default MenuItem;
|
||||||
|
|||||||
@@ -29,6 +29,12 @@
|
|||||||
overflow: hidden !important;
|
overflow: hidden !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modalOpenIOS {
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Sizes
|
* Sizes
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import ReactDOM from 'react-dom';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import elementClass from 'element-class';
|
import elementClass from 'element-class';
|
||||||
import getUniqueElememtId from 'Utilities/getUniqueElementId';
|
import getUniqueElememtId from 'Utilities/getUniqueElementId';
|
||||||
|
import { isIOS } from 'Utilities/mobile';
|
||||||
|
import { setScrollLock } from 'Utilities/scrollLock';
|
||||||
import * as keyCodes from 'Utilities/Constants/keyCodes';
|
import * as keyCodes from 'Utilities/Constants/keyCodes';
|
||||||
import { sizes } from 'Helpers/Props';
|
import { sizes } from 'Helpers/Props';
|
||||||
import ErrorBoundary from 'Components/Error/ErrorBoundary';
|
import ErrorBoundary from 'Components/Error/ErrorBoundary';
|
||||||
@@ -31,6 +33,7 @@ class Modal extends Component {
|
|||||||
this._node = document.getElementById('portal-root');
|
this._node = document.getElementById('portal-root');
|
||||||
this._backgroundRef = null;
|
this._backgroundRef = null;
|
||||||
this._modalId = getUniqueElememtId();
|
this._modalId = getUniqueElememtId();
|
||||||
|
this._bodyScrollTop = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -69,7 +72,14 @@ class Modal extends Component {
|
|||||||
window.addEventListener('keydown', this.onKeyDown);
|
window.addEventListener('keydown', this.onKeyDown);
|
||||||
|
|
||||||
if (openModals.length === 1) {
|
if (openModals.length === 1) {
|
||||||
elementClass(document.body).add(styles.modalOpen);
|
if (isIOS()) {
|
||||||
|
setScrollLock(true);
|
||||||
|
const scrollTop = document.body.scrollTop;
|
||||||
|
this._bodyScrollTop = scrollTop;
|
||||||
|
elementClass(document.body).add(styles.modalOpenIOS);
|
||||||
|
} else {
|
||||||
|
elementClass(document.body).add(styles.modalOpen);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +88,14 @@ class Modal extends Component {
|
|||||||
window.removeEventListener('keydown', this.onKeyDown);
|
window.removeEventListener('keydown', this.onKeyDown);
|
||||||
|
|
||||||
if (openModals.length === 0) {
|
if (openModals.length === 0) {
|
||||||
elementClass(document.body).remove(styles.modalOpen);
|
setScrollLock(false);
|
||||||
|
|
||||||
|
if (isIOS()) {
|
||||||
|
elementClass(document.body).remove(styles.modalOpenIOS);
|
||||||
|
document.body.scrollTop = this._bodyScrollTop;
|
||||||
|
} else {
|
||||||
|
elementClass(document.body).remove(styles.modalOpen);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ ModalBody.propTypes = {
|
|||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
innerClassName: PropTypes.string,
|
innerClassName: PropTypes.string,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
scrollDirection: PropTypes.oneOf([scrollDirections.NONE, scrollDirections.HORIZONTAL, scrollDirections.VERTICAL])
|
scrollDirection: PropTypes.oneOf(scrollDirections.all)
|
||||||
};
|
};
|
||||||
|
|
||||||
ModalBody.defaultProps = {
|
ModalBody.defaultProps = {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { isLocked } from 'Utilities/scrollLock';
|
||||||
import { scrollDirections } from 'Helpers/Props';
|
import { scrollDirections } from 'Helpers/Props';
|
||||||
import OverlayScroller from 'Components/Scroller/OverlayScroller';
|
import OverlayScroller from 'Components/Scroller/OverlayScroller';
|
||||||
import Scroller from 'Components/Scroller/Scroller';
|
import Scroller from 'Components/Scroller/Scroller';
|
||||||
@@ -7,6 +8,17 @@ import styles from './PageContentBody.css';
|
|||||||
|
|
||||||
class PageContentBody extends Component {
|
class PageContentBody extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onScroll = (props) => {
|
||||||
|
const { onScroll } = this.props;
|
||||||
|
|
||||||
|
if (this.props.onScroll && !isLocked()) {
|
||||||
|
onScroll(props);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
@@ -27,6 +39,7 @@ class PageContentBody extends Component {
|
|||||||
className={className}
|
className={className}
|
||||||
scrollDirection={scrollDirections.VERTICAL}
|
scrollDirection={scrollDirections.VERTICAL}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
|
onScroll={this.onScroll}
|
||||||
>
|
>
|
||||||
<div className={innerClassName}>
|
<div className={innerClassName}>
|
||||||
{children}
|
{children}
|
||||||
@@ -41,6 +54,7 @@ PageContentBody.propTypes = {
|
|||||||
innerClassName: PropTypes.string,
|
innerClassName: PropTypes.string,
|
||||||
isSmallScreen: PropTypes.bool.isRequired,
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.node.isRequired,
|
||||||
|
onScroll: PropTypes.func,
|
||||||
dispatch: PropTypes.func
|
dispatch: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -14,4 +14,4 @@ function createMapStateToProps() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(createMapStateToProps)(PageContentBody);
|
export default connect(createMapStateToProps, null, null, { forwardRef: true })(PageContentBody);
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ PageSidebarItem.propTypes = {
|
|||||||
isActiveParent: PropTypes.bool,
|
isActiveParent: PropTypes.bool,
|
||||||
isParentItem: PropTypes.bool.isRequired,
|
isParentItem: PropTypes.bool.isRequired,
|
||||||
isChildItem: PropTypes.bool.isRequired,
|
isChildItem: PropTypes.bool.isRequired,
|
||||||
statusComponent: PropTypes.func,
|
statusComponent: PropTypes.elementType,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
onPress: PropTypes.func
|
onPress: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.thumb {
|
.thumb {
|
||||||
min-height: 50px;
|
min-height: 100px;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
background-color: $scrollbarBackgroundColor;
|
background-color: $scrollbarBackgroundColor;
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { Scrollbars } from 'react-custom-scrollbars';
|
|||||||
import { scrollDirections } from 'Helpers/Props';
|
import { scrollDirections } from 'Helpers/Props';
|
||||||
import styles from './OverlayScroller.css';
|
import styles from './OverlayScroller.css';
|
||||||
|
|
||||||
|
const SCROLLBAR_SIZE = 10;
|
||||||
|
|
||||||
class OverlayScroller extends Component {
|
class OverlayScroller extends Component {
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -21,7 +23,11 @@ class OverlayScroller extends Component {
|
|||||||
scrollTop
|
scrollTop
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (!this._isScrolling && scrollTop != null && scrollTop !== prevProps.scrollTop) {
|
if (
|
||||||
|
!this._isScrolling &&
|
||||||
|
scrollTop != null &&
|
||||||
|
scrollTop !== prevProps.scrollTop
|
||||||
|
) {
|
||||||
this._scroller.scrollTop(scrollTop);
|
this._scroller.scrollTop(scrollTop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,6 +48,42 @@ class OverlayScroller extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_renderTrackHorizontal = ({ style, props }) => {
|
||||||
|
const finalStyle = {
|
||||||
|
...style,
|
||||||
|
right: 2,
|
||||||
|
bottom: 2,
|
||||||
|
left: 2,
|
||||||
|
borderRadius: 3,
|
||||||
|
height: SCROLLBAR_SIZE
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={finalStyle}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderTrackVertical = ({ style, props }) => {
|
||||||
|
const finalStyle = {
|
||||||
|
...style,
|
||||||
|
right: 2,
|
||||||
|
bottom: 2,
|
||||||
|
top: 2,
|
||||||
|
borderRadius: 3,
|
||||||
|
width: SCROLLBAR_SIZE
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={finalStyle}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
_renderView = (props) => {
|
_renderView = (props) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -91,6 +133,8 @@ class OverlayScroller extends Component {
|
|||||||
ref={this._setScrollRef}
|
ref={this._setScrollRef}
|
||||||
autoHide={autoHide}
|
autoHide={autoHide}
|
||||||
hideTracksWhenNotNeeded={autoScroll}
|
hideTracksWhenNotNeeded={autoScroll}
|
||||||
|
renderTrackHorizontal={this._renderTrackHorizontal}
|
||||||
|
renderTrackVertical={this._renderTrackVertical}
|
||||||
renderThumbHorizontal={this._renderThumb}
|
renderThumbHorizontal={this._renderThumb}
|
||||||
renderThumbVertical={this._renderThumb}
|
renderThumbVertical={this._renderThumb}
|
||||||
renderView={this._renderView}
|
renderView={this._renderView}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
@add-mixin scrollbar;
|
@add-mixin scrollbar;
|
||||||
@add-mixin scrollbarTrack;
|
@add-mixin scrollbarTrack;
|
||||||
@add-mixin scrollbarThumb;
|
@add-mixin scrollbarThumb;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.none {
|
.none {
|
||||||
@@ -26,3 +27,11 @@
|
|||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.both {
|
||||||
|
overflow: scroll;
|
||||||
|
|
||||||
|
&.autoScroll {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class Scroller extends Component {
|
|||||||
|
|
||||||
Scroller.propTypes = {
|
Scroller.propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
scrollDirection: PropTypes.oneOf([scrollDirections.NONE, scrollDirections.HORIZONTAL, scrollDirections.VERTICAL]).isRequired,
|
scrollDirection: PropTypes.oneOf(scrollDirections.all).isRequired,
|
||||||
autoScroll: PropTypes.bool.isRequired,
|
autoScroll: PropTypes.bool.isRequired,
|
||||||
scrollTop: PropTypes.number,
|
scrollTop: PropTypes.number,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import titleCase from 'Utilities/String/titleCase';
|
|||||||
import { fetchCommands, updateCommand, finishCommand } from 'Store/Actions/commandActions';
|
import { fetchCommands, updateCommand, finishCommand } from 'Store/Actions/commandActions';
|
||||||
import { setAppValue, setVersion } from 'Store/Actions/appActions';
|
import { setAppValue, setVersion } from 'Store/Actions/appActions';
|
||||||
import { update, updateItem, removeItem } from 'Store/Actions/baseActions';
|
import { update, updateItem, removeItem } from 'Store/Actions/baseActions';
|
||||||
|
import { fetchSeries } from 'Store/Actions/seriesActions';
|
||||||
import { fetchHealth } from 'Store/Actions/systemActions';
|
import { fetchHealth } from 'Store/Actions/systemActions';
|
||||||
import { fetchQueue, fetchQueueDetails } from 'Store/Actions/queueActions';
|
import { fetchQueue, fetchQueueDetails } from 'Store/Actions/queueActions';
|
||||||
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||||
@@ -72,6 +73,7 @@ const mapDispatchToProps = {
|
|||||||
dispatchFetchQueue: fetchQueue,
|
dispatchFetchQueue: fetchQueue,
|
||||||
dispatchFetchQueueDetails: fetchQueueDetails,
|
dispatchFetchQueueDetails: fetchQueueDetails,
|
||||||
dispatchFetchRootFolders: fetchRootFolders,
|
dispatchFetchRootFolders: fetchRootFolders,
|
||||||
|
dispatchFetchSeries: fetchSeries,
|
||||||
dispatchFetchTags: fetchTags,
|
dispatchFetchTags: fetchTags,
|
||||||
dispatchFetchTagDetails: fetchTagDetails
|
dispatchFetchTagDetails: fetchTagDetails
|
||||||
};
|
};
|
||||||
@@ -288,6 +290,7 @@ class SignalRConnector extends Component {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
dispatchFetchCommands,
|
dispatchFetchCommands,
|
||||||
|
dispatchFetchSeries,
|
||||||
dispatchSetAppValue
|
dispatchSetAppValue
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@@ -295,6 +298,7 @@ class SignalRConnector extends Component {
|
|||||||
// are in sync after reconnecting.
|
// are in sync after reconnecting.
|
||||||
|
|
||||||
if (this.props.isReconnecting || this.props.isDisconnected) {
|
if (this.props.isReconnecting || this.props.isDisconnected) {
|
||||||
|
dispatchFetchSeries();
|
||||||
dispatchFetchCommands();
|
dispatchFetchCommands();
|
||||||
repopulatePage();
|
repopulatePage();
|
||||||
}
|
}
|
||||||
@@ -376,6 +380,7 @@ SignalRConnector.propTypes = {
|
|||||||
dispatchFetchQueue: PropTypes.func.isRequired,
|
dispatchFetchQueue: PropTypes.func.isRequired,
|
||||||
dispatchFetchQueueDetails: PropTypes.func.isRequired,
|
dispatchFetchQueueDetails: PropTypes.func.isRequired,
|
||||||
dispatchFetchRootFolders: PropTypes.func.isRequired,
|
dispatchFetchRootFolders: PropTypes.func.isRequired,
|
||||||
|
dispatchFetchSeries: PropTypes.func.isRequired,
|
||||||
dispatchFetchTags: PropTypes.func.isRequired,
|
dispatchFetchTags: PropTypes.func.isRequired,
|
||||||
dispatchFetchTagDetails: PropTypes.func.isRequired
|
dispatchFetchTagDetails: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ RelativeDateCell.propTypes = {
|
|||||||
shortDateFormat: PropTypes.string.isRequired,
|
shortDateFormat: PropTypes.string.isRequired,
|
||||||
longDateFormat: PropTypes.string.isRequired,
|
longDateFormat: PropTypes.string.isRequired,
|
||||||
timeFormat: PropTypes.string.isRequired,
|
timeFormat: PropTypes.string.isRequired,
|
||||||
component: PropTypes.func,
|
component: PropTypes.elementType,
|
||||||
dispatch: PropTypes.func
|
dispatch: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
.tableContainer {
|
.tableContainer {
|
||||||
overflow-x: auto;
|
&.horizontalScroll {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.table {
|
.table {
|
||||||
@@ -10,7 +12,12 @@
|
|||||||
|
|
||||||
@media only screen and (max-width: $breakpointSmall) {
|
@media only screen and (max-width: $breakpointSmall) {
|
||||||
.tableContainer {
|
.tableContainer {
|
||||||
overflow-y: hidden;
|
min-width: 100%;
|
||||||
width: 100%;
|
width: fit-content;
|
||||||
|
|
||||||
|
&.horizontalScroll {
|
||||||
|
overflow-y: hidden;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
import { icons, scrollDirections } from 'Helpers/Props';
|
import { icons, scrollDirections } from 'Helpers/Props';
|
||||||
import IconButton from 'Components/Link/IconButton';
|
import IconButton from 'Components/Link/IconButton';
|
||||||
import Scroller from 'Components/Scroller/Scroller';
|
import Scroller from 'Components/Scroller/Scroller';
|
||||||
@@ -28,6 +29,7 @@ function getTableHeaderCellProps(props) {
|
|||||||
function Table(props) {
|
function Table(props) {
|
||||||
const {
|
const {
|
||||||
className,
|
className,
|
||||||
|
horizontalScroll,
|
||||||
selectAll,
|
selectAll,
|
||||||
columns,
|
columns,
|
||||||
optionsComponent,
|
optionsComponent,
|
||||||
@@ -41,14 +43,22 @@ function Table(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Scroller
|
<Scroller
|
||||||
className={styles.tableContainer}
|
className={classNames(
|
||||||
scrollDirection={scrollDirections.HORIZONTAL}
|
styles.tableContainer,
|
||||||
|
horizontalScroll && styles.horizontalScroll
|
||||||
|
)}
|
||||||
|
scrollDirection={
|
||||||
|
horizontalScroll ?
|
||||||
|
scrollDirections.HORIZONTAL :
|
||||||
|
scrollDirections.NONE
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<table className={className}>
|
<table className={className}>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
{
|
{
|
||||||
selectAll &&
|
selectAll ?
|
||||||
<TableSelectAllHeaderCell {...otherProps} />
|
<TableSelectAllHeaderCell {...otherProps} /> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -111,9 +121,10 @@ function Table(props) {
|
|||||||
|
|
||||||
Table.propTypes = {
|
Table.propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
|
horizontalScroll: PropTypes.bool.isRequired,
|
||||||
selectAll: PropTypes.bool.isRequired,
|
selectAll: PropTypes.bool.isRequired,
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
optionsComponent: PropTypes.func,
|
optionsComponent: PropTypes.elementType,
|
||||||
pageSize: PropTypes.number,
|
pageSize: PropTypes.number,
|
||||||
canModifyColumns: PropTypes.bool,
|
canModifyColumns: PropTypes.bool,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
@@ -123,6 +134,7 @@ Table.propTypes = {
|
|||||||
|
|
||||||
Table.defaultProps = {
|
Table.defaultProps = {
|
||||||
className: styles.table,
|
className: styles.table,
|
||||||
|
horizontalScroll: true,
|
||||||
selectAll: false
|
selectAll: false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { DragDropContext } from 'react-dnd';
|
import { DndProvider } from 'react-dnd';
|
||||||
import HTML5Backend from 'react-dnd-html5-backend';
|
import HTML5Backend from 'react-dnd-html5-backend';
|
||||||
import { inputTypes } from 'Helpers/Props';
|
import { inputTypes } from 'Helpers/Props';
|
||||||
import Button from 'Components/Link/Button';
|
import Button from 'Components/Link/Button';
|
||||||
@@ -127,116 +127,118 @@ class TableOptionsModal extends Component {
|
|||||||
const isDraggingDown = isDragging && dropIndex > dragIndex;
|
const isDraggingDown = isDragging && dropIndex > dragIndex;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<DndProvider backend={HTML5Backend}>
|
||||||
isOpen={isOpen}
|
<Modal
|
||||||
onModalClose={onModalClose}
|
isOpen={isOpen}
|
||||||
>
|
onModalClose={onModalClose}
|
||||||
{
|
>
|
||||||
isOpen ?
|
{
|
||||||
<ModalContent onModalClose={onModalClose}>
|
isOpen ?
|
||||||
<ModalHeader>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
|
<ModalHeader>
|
||||||
Table Options
|
Table Options
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<Form>
|
<Form>
|
||||||
{
|
{
|
||||||
hasPageSize ?
|
hasPageSize ?
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>Page Size</FormLabel>
|
<FormLabel>Page Size</FormLabel>
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.NUMBER}
|
type={inputTypes.NUMBER}
|
||||||
name="pageSize"
|
name="pageSize"
|
||||||
value={pageSize || 0}
|
value={pageSize || 0}
|
||||||
helpText="Number of items to show on each page"
|
helpText="Number of items to show on each page"
|
||||||
errors={pageSizeError ? [{ message: pageSizeError }] : undefined}
|
errors={pageSizeError ? [{ message: pageSizeError }] : undefined}
|
||||||
onChange={this.onPageSizeChange}
|
onChange={this.onPageSizeChange}
|
||||||
/>
|
|
||||||
</FormGroup> :
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
OptionsComponent ?
|
|
||||||
<OptionsComponent
|
|
||||||
onTableOptionChange={onTableOptionChange}
|
|
||||||
/> : null
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
canModifyColumns ?
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>Columns</FormLabel>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<FormInputHelpText
|
|
||||||
text="Choose which columns are visible and which order they appear in"
|
|
||||||
/>
|
/>
|
||||||
|
</FormGroup> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
<div className={styles.columns}>
|
{
|
||||||
{
|
OptionsComponent ?
|
||||||
columns.map((column, index) => {
|
<OptionsComponent
|
||||||
const {
|
onTableOptionChange={onTableOptionChange}
|
||||||
name,
|
/> : null
|
||||||
label,
|
}
|
||||||
columnLabel,
|
|
||||||
isVisible,
|
{
|
||||||
isModifiable
|
canModifyColumns ?
|
||||||
} = column;
|
<FormGroup>
|
||||||
|
<FormLabel>Columns</FormLabel>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<FormInputHelpText
|
||||||
|
text="Choose which columns are visible and which order they appear in"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className={styles.columns}>
|
||||||
|
{
|
||||||
|
columns.map((column, index) => {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
columnLabel,
|
||||||
|
isVisible,
|
||||||
|
isModifiable
|
||||||
|
} = column;
|
||||||
|
|
||||||
|
if (isModifiable !== false) {
|
||||||
|
return (
|
||||||
|
<TableOptionsColumnDragSource
|
||||||
|
key={name}
|
||||||
|
name={name}
|
||||||
|
label={label || columnLabel}
|
||||||
|
isVisible={isVisible}
|
||||||
|
isModifiable={true}
|
||||||
|
index={index}
|
||||||
|
isDragging={isDragging}
|
||||||
|
isDraggingUp={isDraggingUp}
|
||||||
|
isDraggingDown={isDraggingDown}
|
||||||
|
onVisibleChange={this.onVisibleChange}
|
||||||
|
onColumnDragMove={this.onColumnDragMove}
|
||||||
|
onColumnDragEnd={this.onColumnDragEnd}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (isModifiable !== false) {
|
|
||||||
return (
|
return (
|
||||||
<TableOptionsColumnDragSource
|
<TableOptionsColumn
|
||||||
key={name}
|
key={name}
|
||||||
name={name}
|
name={name}
|
||||||
label={label || columnLabel}
|
label={label || columnLabel}
|
||||||
isVisible={isVisible}
|
isVisible={isVisible}
|
||||||
isModifiable={true}
|
|
||||||
index={index}
|
index={index}
|
||||||
isDragging={isDragging}
|
isModifiable={false}
|
||||||
isDraggingUp={isDraggingUp}
|
|
||||||
isDraggingDown={isDraggingDown}
|
|
||||||
onVisibleChange={this.onVisibleChange}
|
onVisibleChange={this.onVisibleChange}
|
||||||
onColumnDragMove={this.onColumnDragMove}
|
|
||||||
onColumnDragEnd={this.onColumnDragEnd}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
<TableOptionsColumnDragPreview />
|
||||||
<TableOptionsColumn
|
</div>
|
||||||
key={name}
|
|
||||||
name={name}
|
|
||||||
label={label || columnLabel}
|
|
||||||
isVisible={isVisible}
|
|
||||||
index={index}
|
|
||||||
isModifiable={false}
|
|
||||||
onVisibleChange={this.onVisibleChange}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
<TableOptionsColumnDragPreview />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</FormGroup> :
|
||||||
</FormGroup> :
|
null
|
||||||
null
|
}
|
||||||
}
|
</Form>
|
||||||
</Form>
|
</ModalBody>
|
||||||
</ModalBody>
|
<ModalFooter>
|
||||||
<ModalFooter>
|
<Button
|
||||||
<Button
|
onPress={onModalClose}
|
||||||
onPress={onModalClose}
|
>
|
||||||
>
|
|
||||||
Close
|
Close
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent> :
|
</ModalContent> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
</DndProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,7 +248,7 @@ TableOptionsModal.propTypes = {
|
|||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
pageSize: PropTypes.number,
|
pageSize: PropTypes.number,
|
||||||
canModifyColumns: PropTypes.bool.isRequired,
|
canModifyColumns: PropTypes.bool.isRequired,
|
||||||
optionsComponent: PropTypes.func,
|
optionsComponent: PropTypes.elementType,
|
||||||
onTableOptionChange: PropTypes.func.isRequired,
|
onTableOptionChange: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
@@ -255,4 +257,4 @@ TableOptionsModal.defaultProps = {
|
|||||||
canModifyColumns: true
|
canModifyColumns: true
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DragDropContext(HTML5Backend)(TableOptionsModal);
|
export default TableOptionsModal;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { WindowScroller } from 'react-virtualized';
|
import { WindowScroller } from 'react-virtualized';
|
||||||
|
import { isLocked } from 'Utilities/scrollLock';
|
||||||
import { scrollDirections } from 'Helpers/Props';
|
import { scrollDirections } from 'Helpers/Props';
|
||||||
import Measure from 'Components/Measure';
|
import Measure from 'Components/Measure';
|
||||||
import Scroller from 'Components/Scroller/Scroller';
|
import Scroller from 'Components/Scroller/Scroller';
|
||||||
@@ -51,10 +52,10 @@ class VirtualTable extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps, preState) {
|
componentDidUpdate(prevProps, preState) {
|
||||||
const scrollIndex = this.props.scrollIndex;
|
const { scrollIndex, rowHeight } = this.props;
|
||||||
|
|
||||||
if (scrollIndex != null && scrollIndex !== prevProps.scrollIndex) {
|
if (scrollIndex != null && scrollIndex !== prevProps.scrollIndex) {
|
||||||
const scrollTop = (scrollIndex + 1) * ROW_HEIGHT + 20;
|
const scrollTop = (scrollIndex + 1) * rowHeight + 20;
|
||||||
|
|
||||||
this.props.onScroll({ scrollTop });
|
this.props.onScroll({ scrollTop });
|
||||||
}
|
}
|
||||||
@@ -83,6 +84,16 @@ class VirtualTable extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onScroll = (props) => {
|
||||||
|
if (isLocked()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { onScroll } = this.props;
|
||||||
|
|
||||||
|
onScroll(props);
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
@@ -107,7 +118,7 @@ class VirtualTable extends Component {
|
|||||||
<Measure onMeasure={this.onMeasure}>
|
<Measure onMeasure={this.onMeasure}>
|
||||||
<WindowScroller
|
<WindowScroller
|
||||||
scrollElement={isSmallScreen ? undefined : this._contentBodyNode}
|
scrollElement={isSmallScreen ? undefined : this._contentBodyNode}
|
||||||
onScroll={onScroll}
|
onScroll={this.onScroll}
|
||||||
>
|
>
|
||||||
{({ height, isScrolling }) => {
|
{({ height, isScrolling }) => {
|
||||||
return (
|
return (
|
||||||
@@ -154,6 +165,7 @@ VirtualTable.propTypes = {
|
|||||||
header: PropTypes.node.isRequired,
|
header: PropTypes.node.isRequired,
|
||||||
headerHeight: PropTypes.number.isRequired,
|
headerHeight: PropTypes.number.isRequired,
|
||||||
rowRenderer: PropTypes.func.isRequired,
|
rowRenderer: PropTypes.func.isRequired,
|
||||||
|
rowHeight: PropTypes.number.isRequired,
|
||||||
onRender: PropTypes.func.isRequired,
|
onRender: PropTypes.func.isRequired,
|
||||||
onScroll: PropTypes.func.isRequired
|
onScroll: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { Manager, Popper, Reference } from 'react-popper';
|
import { Manager, Popper, Reference } from 'react-popper';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import isMobileUtil from 'Utilities/isMobile';
|
import { isMobile as isMobileUtil } from 'Utilities/mobile';
|
||||||
import { kinds, tooltipPositions } from 'Helpers/Props';
|
import { kinds, tooltipPositions } from 'Helpers/Props';
|
||||||
import Portal from 'Components/Portal';
|
import Portal from 'Components/Portal';
|
||||||
import styles from './Tooltip.css';
|
import styles from './Tooltip.css';
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 12 KiB |
@@ -72,6 +72,8 @@ function createMapStateToProps() {
|
|||||||
relativePath: episodeFile.relativePath,
|
relativePath: episodeFile.relativePath,
|
||||||
language: episodeFile.language,
|
language: episodeFile.language,
|
||||||
quality: episodeFile.quality,
|
quality: episodeFile.quality,
|
||||||
|
languageCutoffNotMet: episodeFile.languageCutoffNotMet,
|
||||||
|
qualityCutoffNotMet: episodeFile.qualityCutoffNotMet,
|
||||||
...episode
|
...episode
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import padNumber from 'Utilities/Number/padNumber';
|
import padNumber from 'Utilities/Number/padNumber';
|
||||||
import Label from 'Components/Label';
|
|
||||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||||
import TableRow from 'Components/Table/TableRow';
|
import TableRow from 'Components/Table/TableRow';
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
||||||
|
import EpisodeLanguage from 'Episode/EpisodeLanguage';
|
||||||
import EpisodeQuality from 'Episode/EpisodeQuality';
|
import EpisodeQuality from 'Episode/EpisodeQuality';
|
||||||
import styles from './EpisodeFileEditorRow';
|
import styles from './EpisodeFileEditorRow';
|
||||||
|
|
||||||
@@ -20,6 +20,8 @@ function EpisodeFileEditorRow(props) {
|
|||||||
airDateUtc,
|
airDateUtc,
|
||||||
language,
|
language,
|
||||||
quality,
|
quality,
|
||||||
|
qualityCutoffNotMet,
|
||||||
|
languageCutoffNotMet,
|
||||||
isSelected,
|
isSelected,
|
||||||
onSelectedChange
|
onSelectedChange
|
||||||
} = props;
|
} = props;
|
||||||
@@ -52,14 +54,16 @@ function EpisodeFileEditorRow(props) {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<TableRowCell>
|
<TableRowCell>
|
||||||
<Label>
|
<EpisodeLanguage
|
||||||
{language.name}
|
language={language}
|
||||||
</Label>
|
isCutoffNotMet={languageCutoffNotMet}
|
||||||
|
/>
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
<TableRowCell>
|
<TableRowCell>
|
||||||
<EpisodeQuality
|
<EpisodeQuality
|
||||||
quality={quality}
|
quality={quality}
|
||||||
|
isCutoffNotMet={qualityCutoffNotMet}
|
||||||
/>
|
/>
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -76,6 +80,8 @@ EpisodeFileEditorRow.propTypes = {
|
|||||||
airDateUtc: PropTypes.string.isRequired,
|
airDateUtc: PropTypes.string.isRequired,
|
||||||
language: PropTypes.object.isRequired,
|
language: PropTypes.object.isRequired,
|
||||||
quality: PropTypes.object.isRequired,
|
quality: PropTypes.object.isRequired,
|
||||||
|
qualityCutoffNotMet: PropTypes.bool.isRequired,
|
||||||
|
languageCutoffNotMet: PropTypes.bool.isRequired,
|
||||||
isSelected: PropTypes.bool,
|
isSelected: PropTypes.bool,
|
||||||
onSelectedChange: PropTypes.func.isRequired
|
onSelectedChange: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -181,6 +181,7 @@ export const SCORE = fasUserPlus;
|
|||||||
export const SEARCH = fasSearch;
|
export const SEARCH = fasSearch;
|
||||||
export const SERIES_CONTINUING = fasPlay;
|
export const SERIES_CONTINUING = fasPlay;
|
||||||
export const SERIES_ENDED = fasStop;
|
export const SERIES_ENDED = fasStop;
|
||||||
|
export const SERIES_DELETED = fasExclamationTriangle;
|
||||||
export const SETTINGS = fasCogs;
|
export const SETTINGS = fasCogs;
|
||||||
export const SHUTDOWN = fasPowerOff;
|
export const SHUTDOWN = fasPowerOff;
|
||||||
export const SORT = fasSort;
|
export const SORT = fasSort;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export const NONE = 'none';
|
export const NONE = 'none';
|
||||||
|
export const BOTH = 'both';
|
||||||
export const HORIZONTAL = 'horizontal';
|
export const HORIZONTAL = 'horizontal';
|
||||||
export const VERTICAL = 'vertical';
|
export const VERTICAL = 'vertical';
|
||||||
|
|
||||||
export const all = [NONE, HORIZONTAL, VERTICAL];
|
export const all = [NONE, HORIZONTAL, VERTICAL, BOTH];
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
.footer {
|
||||||
|
composes: modalFooter from '~Components/Modal/ModalFooter.css';
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path {
|
||||||
|
margin-right: 20px;
|
||||||
|
color: $dimColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: $breakpointSmall) {
|
||||||
|
.footer {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path {
|
||||||
|
margin-right: 0;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
|
|||||||
import Table from 'Components/Table/Table';
|
import Table from 'Components/Table/Table';
|
||||||
import TableBody from 'Components/Table/TableBody';
|
import TableBody from 'Components/Table/TableBody';
|
||||||
import SelectEpisodeRow from './SelectEpisodeRow';
|
import SelectEpisodeRow from './SelectEpisodeRow';
|
||||||
|
import styles from './SelectEpisodeModalContent.css';
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@@ -79,10 +80,12 @@ class SelectEpisodeModalContent extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
|
ids,
|
||||||
isFetching,
|
isFetching,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
error,
|
error,
|
||||||
items,
|
items,
|
||||||
|
relativePath,
|
||||||
sortKey,
|
sortKey,
|
||||||
sortDirection,
|
sortDirection,
|
||||||
onSortPress,
|
onSortPress,
|
||||||
@@ -97,10 +100,20 @@ class SelectEpisodeModalContent extends Component {
|
|||||||
|
|
||||||
const errorMessage = getErrorMessage(error, 'Unable to load episodes');
|
const errorMessage = getErrorMessage(error, 'Unable to load episodes');
|
||||||
|
|
||||||
|
const selectedFilesCount = ids.length;
|
||||||
|
const selectedCount = this.getSelectedIds().length;
|
||||||
|
const selectionIsValid = (
|
||||||
|
selectedCount > 0 &&
|
||||||
|
selectedCount % selectedFilesCount === 0
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
Manual Import - Select Episode(s)
|
<div className={styles.header}>
|
||||||
|
Manual Import - Select Episode(s)
|
||||||
|
</div>
|
||||||
|
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
@@ -152,17 +165,28 @@ class SelectEpisodeModalContent extends Component {
|
|||||||
}
|
}
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|
||||||
<ModalFooter>
|
<ModalFooter className={styles.footer}>
|
||||||
<Button onPress={onModalClose}>
|
<div className={styles.path}>
|
||||||
Cancel
|
{
|
||||||
</Button>
|
relativePath ?
|
||||||
|
relativePath :
|
||||||
|
`${selectedFilesCount} selected files`
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
<Button
|
<div className={styles.buttons}>
|
||||||
kind={kinds.SUCCESS}
|
<Button onPress={onModalClose}>
|
||||||
onPress={this.onEpisodesSelect}
|
Cancel
|
||||||
>
|
</Button>
|
||||||
Select Episodes
|
|
||||||
</Button>
|
<Button
|
||||||
|
kind={kinds.SUCCESS}
|
||||||
|
isDisabled={!selectionIsValid}
|
||||||
|
onPress={this.onEpisodesSelect}
|
||||||
|
>
|
||||||
|
Select Episodes
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
);
|
);
|
||||||
@@ -170,10 +194,12 @@ class SelectEpisodeModalContent extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SelectEpisodeModalContent.propTypes = {
|
SelectEpisodeModalContent.propTypes = {
|
||||||
|
ids: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
isFetching: PropTypes.bool.isRequired,
|
isFetching: PropTypes.bool.isRequired,
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
error: PropTypes.object,
|
error: PropTypes.object,
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
relativePath: PropTypes.string,
|
||||||
sortKey: PropTypes.string,
|
sortKey: PropTypes.string,
|
||||||
sortDirection: PropTypes.string,
|
sortDirection: PropTypes.string,
|
||||||
onSortPress: PropTypes.func.isRequired,
|
onSortPress: PropTypes.func.isRequired,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
@@ -56,7 +55,13 @@ class SelectEpisodeModalContentConnector extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onEpisodesSelect = (episodeIds) => {
|
onEpisodesSelect = (episodeIds) => {
|
||||||
const episodes = _.reduce(this.props.items, (acc, item) => {
|
const {
|
||||||
|
ids,
|
||||||
|
items,
|
||||||
|
onModalClose
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const selectedEpisodes = items.reduce((acc, item) => {
|
||||||
if (episodeIds.indexOf(item.id) > -1) {
|
if (episodeIds.indexOf(item.id) > -1) {
|
||||||
acc.push(item);
|
acc.push(item);
|
||||||
}
|
}
|
||||||
@@ -64,12 +69,22 @@ class SelectEpisodeModalContentConnector extends Component {
|
|||||||
return acc;
|
return acc;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
this.props.updateInteractiveImportItem({
|
const episodesPerFile = selectedEpisodes.length / ids.length;
|
||||||
id: this.props.id,
|
const sortedEpisodes = selectedEpisodes.sort((a, b) => {
|
||||||
episodes: _.sortBy(episodes, 'episodeNumber')
|
return a.seasonNumber - b.seasonNumber;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.props.onModalClose(true);
|
ids.forEach((id, index) => {
|
||||||
|
const startingIndex = index * episodesPerFile;
|
||||||
|
const episodes = sortedEpisodes.slice(startingIndex, startingIndex + episodesPerFile);
|
||||||
|
|
||||||
|
this.props.updateInteractiveImportItem({
|
||||||
|
id,
|
||||||
|
episodes
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onModalClose(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -87,7 +102,7 @@ class SelectEpisodeModalContentConnector extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SelectEpisodeModalContentConnector.propTypes = {
|
SelectEpisodeModalContentConnector.propTypes = {
|
||||||
id: PropTypes.number.isRequired,
|
ids: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
seriesId: PropTypes.number.isRequired,
|
seriesId: PropTypes.number.isRequired,
|
||||||
seasonNumber: PropTypes.number.isRequired,
|
seasonNumber: PropTypes.number.isRequired,
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
|||||||
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||||
import selectAll from 'Utilities/Table/selectAll';
|
import selectAll from 'Utilities/Table/selectAll';
|
||||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||||
import { align, icons, kinds } from 'Helpers/Props';
|
import { align, icons, kinds, scrollDirections } from 'Helpers/Props';
|
||||||
import Button from 'Components/Link/Button';
|
import Button from 'Components/Link/Button';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
@@ -20,6 +20,7 @@ import ModalBody from 'Components/Modal/ModalBody';
|
|||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
import Table from 'Components/Table/Table';
|
import Table from 'Components/Table/Table';
|
||||||
import TableBody from 'Components/Table/TableBody';
|
import TableBody from 'Components/Table/TableBody';
|
||||||
|
import SelectEpisodeModal from 'InteractiveImport/Episode/SelectEpisodeModal';
|
||||||
import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal';
|
import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal';
|
||||||
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
|
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
|
||||||
import SelectSeriesModal from 'InteractiveImport/Series/SelectSeriesModal';
|
import SelectSeriesModal from 'InteractiveImport/Series/SelectSeriesModal';
|
||||||
@@ -84,12 +85,13 @@ const filterExistingFilesOptions = {
|
|||||||
|
|
||||||
const importModeOptions = [
|
const importModeOptions = [
|
||||||
{ key: 'move', value: 'Move Files' },
|
{ key: 'move', value: 'Move Files' },
|
||||||
{ key: 'copy', value: 'Copy Files' }
|
{ key: 'copy', value: 'Hardlink/Copy Files' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const SELECT = 'select';
|
const SELECT = 'select';
|
||||||
const SERIES = 'series';
|
const SERIES = 'series';
|
||||||
const SEASON = 'season';
|
const SEASON = 'season';
|
||||||
|
const EPISODE = 'episode';
|
||||||
const LANGUAGE = 'language';
|
const LANGUAGE = 'language';
|
||||||
const QUALITY = 'quality';
|
const QUALITY = 'quality';
|
||||||
|
|
||||||
@@ -208,12 +210,25 @@ class InteractiveImportModalContent extends Component {
|
|||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const selectedIds = this.getSelectedIds();
|
const selectedIds = this.getSelectedIds();
|
||||||
const selectedItem = selectedIds.length ? _.find(items, { id: selectedIds[0] }) : null;
|
|
||||||
|
const orderedSelectedIds = items.reduce((acc, file) => {
|
||||||
|
if (selectedIds.includes(file.id)) {
|
||||||
|
acc.push(file.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const selectedItem = selectedIds.length ?
|
||||||
|
items.find((file) => file.id === selectedIds[0]) :
|
||||||
|
null;
|
||||||
|
|
||||||
const errorMessage = getErrorMessage(error, 'Unable to load manual import items');
|
const errorMessage = getErrorMessage(error, 'Unable to load manual import items');
|
||||||
|
|
||||||
const bulkSelectOptions = [
|
const bulkSelectOptions = [
|
||||||
{ key: SELECT, value: 'Select...', disabled: true },
|
{ key: SELECT, value: 'Select...', disabled: true },
|
||||||
{ key: SEASON, value: 'Select Season' },
|
{ key: SEASON, value: 'Select Season' },
|
||||||
|
{ key: EPISODE, value: 'Select Episode(s)' },
|
||||||
{ key: LANGUAGE, value: 'Select Language' },
|
{ key: LANGUAGE, value: 'Select Language' },
|
||||||
{ key: QUALITY, value: 'Select Quality' }
|
{ key: QUALITY, value: 'Select Quality' }
|
||||||
];
|
];
|
||||||
@@ -231,7 +246,7 @@ class InteractiveImportModalContent extends Component {
|
|||||||
Manual Import - {title || folder}
|
Manual Import - {title || folder}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody scrollDirection={scrollDirections.BOTH}>
|
||||||
{
|
{
|
||||||
showFilterExistingFiles &&
|
showFilterExistingFiles &&
|
||||||
<div className={styles.filterContainer}>
|
<div className={styles.filterContainer}>
|
||||||
@@ -284,6 +299,7 @@ class InteractiveImportModalContent extends Component {
|
|||||||
isPopulated && !!items.length && !isFetching && !isFetching &&
|
isPopulated && !!items.length && !isFetching && !isFetching &&
|
||||||
<Table
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
horizontalScroll={false}
|
||||||
selectAll={true}
|
selectAll={true}
|
||||||
allSelected={allSelected}
|
allSelected={allSelected}
|
||||||
allUnselected={allUnselected}
|
allUnselected={allUnselected}
|
||||||
@@ -374,6 +390,14 @@ class InteractiveImportModalContent extends Component {
|
|||||||
onModalClose={this.onSelectModalClose}
|
onModalClose={this.onSelectModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<SelectEpisodeModal
|
||||||
|
isOpen={selectModalOpen === EPISODE}
|
||||||
|
ids={orderedSelectedIds}
|
||||||
|
seriesId={selectedItem && selectedItem.series && selectedItem.series.id}
|
||||||
|
seasonNumber={selectedItem && selectedItem.seasonNumber}
|
||||||
|
onModalClose={this.onSelectModalClose}
|
||||||
|
/>
|
||||||
|
|
||||||
<SelectLanguageModal
|
<SelectLanguageModal
|
||||||
isOpen={selectModalOpen === LANGUAGE}
|
isOpen={selectModalOpen === LANGUAGE}
|
||||||
ids={selectedIds}
|
ids={selectedIds}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ class InteractiveImportModalContentConnector extends Component {
|
|||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const {
|
const {
|
||||||
downloadId,
|
downloadId,
|
||||||
|
seriesId,
|
||||||
folder
|
folder
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@@ -52,6 +53,7 @@ class InteractiveImportModalContentConnector extends Component {
|
|||||||
|
|
||||||
this.props.dispatchFetchInteractiveImportItems({
|
this.props.dispatchFetchInteractiveImportItems({
|
||||||
downloadId,
|
downloadId,
|
||||||
|
seriesId,
|
||||||
folder,
|
folder,
|
||||||
filterExistingFiles
|
filterExistingFiles
|
||||||
});
|
});
|
||||||
@@ -65,11 +67,13 @@ class InteractiveImportModalContentConnector extends Component {
|
|||||||
if (prevState.filterExistingFiles !== filterExistingFiles) {
|
if (prevState.filterExistingFiles !== filterExistingFiles) {
|
||||||
const {
|
const {
|
||||||
downloadId,
|
downloadId,
|
||||||
|
seriesId,
|
||||||
folder
|
folder
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
this.props.dispatchFetchInteractiveImportItems({
|
this.props.dispatchFetchInteractiveImportItems({
|
||||||
downloadId,
|
downloadId,
|
||||||
|
seriesId,
|
||||||
folder,
|
folder,
|
||||||
filterExistingFiles
|
filterExistingFiles
|
||||||
});
|
});
|
||||||
@@ -185,6 +189,7 @@ class InteractiveImportModalContentConnector extends Component {
|
|||||||
|
|
||||||
InteractiveImportModalContentConnector.propTypes = {
|
InteractiveImportModalContentConnector.propTypes = {
|
||||||
downloadId: PropTypes.string,
|
downloadId: PropTypes.string,
|
||||||
|
seriesId: PropTypes.number,
|
||||||
folder: PropTypes.string,
|
folder: PropTypes.string,
|
||||||
filterExistingFiles: PropTypes.bool.isRequired,
|
filterExistingFiles: PropTypes.bool.isRequired,
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
|||||||
@@ -337,9 +337,10 @@ class InteractiveImportRow extends Component {
|
|||||||
|
|
||||||
<SelectEpisodeModal
|
<SelectEpisodeModal
|
||||||
isOpen={isSelectEpisodeModalOpen}
|
isOpen={isSelectEpisodeModalOpen}
|
||||||
id={id}
|
ids={[id]}
|
||||||
seriesId={series && series.id}
|
seriesId={series && series.id}
|
||||||
seasonNumber={seasonNumber}
|
seasonNumber={seasonNumber}
|
||||||
|
relativePath={relativePath}
|
||||||
onModalClose={this.onSelectEpisodeModalClose}
|
onModalClose={this.onSelectEpisodeModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,21 @@
|
|||||||
|
.protocol {
|
||||||
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.indexer {
|
||||||
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
|
width: 85px;
|
||||||
|
}
|
||||||
|
|
||||||
.quality,
|
.quality,
|
||||||
.language {
|
.language {
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
@@ -36,3 +48,9 @@
|
|||||||
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.peers {
|
||||||
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
|
width: 75px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ class InteractiveSearchRow extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableRowCell>
|
<TableRowCell className={styles.protocol}>
|
||||||
<ProtocolLabel
|
<ProtocolLabel
|
||||||
protocol={protocol}
|
protocol={protocol}
|
||||||
/>
|
/>
|
||||||
@@ -144,7 +144,7 @@ class InteractiveSearchRow extends Component {
|
|||||||
</Link>
|
</Link>
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
<TableRowCell>
|
<TableRowCell className={styles.indexer}>
|
||||||
{indexer}
|
{indexer}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
@@ -152,7 +152,7 @@ class InteractiveSearchRow extends Component {
|
|||||||
{formatBytes(size)}
|
{formatBytes(size)}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
<TableRowCell>
|
<TableRowCell className={styles.peers}>
|
||||||
{
|
{
|
||||||
protocol === 'torrent' &&
|
protocol === 'torrent' &&
|
||||||
<Peers
|
<Peers
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
.link {
|
.link {
|
||||||
composes: link from '~Components/Link/Link.css';
|
composes: link from '~Components/Link/Link.css';
|
||||||
|
}
|
||||||
|
|
||||||
display: block;
|
.unavailablePath {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unavailableLabel {
|
||||||
|
composes: label from '~Components/Label.css';
|
||||||
|
|
||||||
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.freeSpace,
|
.freeSpace,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
import { icons } from 'Helpers/Props';
|
import { icons, kinds } from 'Helpers/Props';
|
||||||
|
import Label from 'Components/Label';
|
||||||
import IconButton from 'Components/Link/IconButton';
|
import IconButton from 'Components/Link/IconButton';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
import TableRow from 'Components/Table/TableRow';
|
import TableRow from 'Components/Table/TableRow';
|
||||||
@@ -12,30 +13,45 @@ function RootFolderRow(props) {
|
|||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
path,
|
path,
|
||||||
|
accessible,
|
||||||
freeSpace,
|
freeSpace,
|
||||||
unmappedFolders,
|
unmappedFolders,
|
||||||
onDeletePress
|
onDeletePress
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const unmappedFoldersCount = unmappedFolders.length || '-';
|
const isUnavailable = !accessible;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableRowCell>
|
<TableRowCell>
|
||||||
<Link
|
{
|
||||||
className={styles.link}
|
isUnavailable ?
|
||||||
to={`/add/import/${id}`}
|
<div className={styles.unavailablePath}>
|
||||||
>
|
{path}
|
||||||
{path}
|
|
||||||
</Link>
|
<Label
|
||||||
|
className={styles.unavailableLabel}
|
||||||
|
kind={kinds.DANGER}
|
||||||
|
>
|
||||||
|
Unavailable
|
||||||
|
</Label>
|
||||||
|
</div> :
|
||||||
|
|
||||||
|
<Link
|
||||||
|
className={styles.link}
|
||||||
|
to={`/add/import/${id}`}
|
||||||
|
>
|
||||||
|
{path}
|
||||||
|
</Link>
|
||||||
|
}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
<TableRowCell className={styles.freeSpace}>
|
<TableRowCell className={styles.freeSpace}>
|
||||||
{formatBytes(freeSpace) || '-'}
|
{(isUnavailable || isNaN(freeSpace)) ? '-' : formatBytes(freeSpace)}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
<TableRowCell className={styles.unmappedFolders}>
|
<TableRowCell className={styles.unmappedFolders}>
|
||||||
{unmappedFoldersCount}
|
{isUnavailable ? '-' : unmappedFolders.length}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
<TableRowCell className={styles.actions}>
|
<TableRowCell className={styles.actions}>
|
||||||
@@ -52,13 +68,13 @@ function RootFolderRow(props) {
|
|||||||
RootFolderRow.propTypes = {
|
RootFolderRow.propTypes = {
|
||||||
id: PropTypes.number.isRequired,
|
id: PropTypes.number.isRequired,
|
||||||
path: PropTypes.string.isRequired,
|
path: PropTypes.string.isRequired,
|
||||||
freeSpace: PropTypes.number.isRequired,
|
accessible: PropTypes.bool.isRequired,
|
||||||
|
freeSpace: PropTypes.number,
|
||||||
unmappedFolders: PropTypes.arrayOf(PropTypes.object).isRequired,
|
unmappedFolders: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
onDeletePress: PropTypes.func.isRequired
|
onDeletePress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
RootFolderRow.defaultProps = {
|
RootFolderRow.defaultProps = {
|
||||||
freeSpace: 0,
|
|
||||||
unmappedFolders: []
|
unmappedFolders: []
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ function RootFolders(props) {
|
|||||||
key={rootFolder.id}
|
key={rootFolder.id}
|
||||||
id={rootFolder.id}
|
id={rootFolder.id}
|
||||||
path={rootFolder.path}
|
path={rootFolder.path}
|
||||||
|
accessible={rootFolder.accessible}
|
||||||
freeSpace={rootFolder.freeSpace}
|
freeSpace={rootFolder.freeSpace}
|
||||||
unmappedFolders={rootFolder.unmappedFolders}
|
unmappedFolders={rootFolder.unmappedFolders}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { icons } from 'Helpers/Props';
|
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
||||||
import TableRow from 'Components/Table/TableRow';
|
import TableRow from 'Components/Table/TableRow';
|
||||||
@@ -8,6 +7,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
|||||||
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
||||||
import SeriesTitleLink from 'Series/SeriesTitleLink';
|
import SeriesTitleLink from 'Series/SeriesTitleLink';
|
||||||
import SeasonPassSeason from './SeasonPassSeason';
|
import SeasonPassSeason from './SeasonPassSeason';
|
||||||
|
import { getSeriesStatusDetails } from 'Series/SeriesStatus';
|
||||||
import styles from './SeasonPassRow.css';
|
import styles from './SeasonPassRow.css';
|
||||||
|
|
||||||
class SeasonPassRow extends Component {
|
class SeasonPassRow extends Component {
|
||||||
@@ -30,6 +30,8 @@ class SeasonPassRow extends Component {
|
|||||||
onSeasonMonitoredPress
|
onSeasonMonitoredPress
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const statusDetails = getSeriesStatusDetails(status);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableSelectCell
|
<TableSelectCell
|
||||||
@@ -41,8 +43,8 @@ class SeasonPassRow extends Component {
|
|||||||
<TableRowCell className={styles.status}>
|
<TableRowCell className={styles.status}>
|
||||||
<Icon
|
<Icon
|
||||||
className={styles.statusIcon}
|
className={styles.statusIcon}
|
||||||
name={status === 'ended' ? icons.SERIES_ENDED : icons.SERIES_CONTINUING}
|
name={statusDetails.icon}
|
||||||
title={status === 'ended' ? 'Ended' : 'Continuing'}
|
title={statusDetails.title}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user