1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2026-03-23 17:14:18 -04:00

Compare commits

..

2 Commits

Author SHA1 Message Date
Taloth Saldono
243c779ca1 Refactored enum and messages. Also added 9p to the list since mono misclassifies it. 2019-06-25 22:59:54 +02:00
Devin Buhl
7ff6342503 disable wal 2019-06-24 21:55:48 -04:00
686 changed files with 27268 additions and 9872 deletions

24
.gitattributes vendored
View File

@@ -1,12 +1,22 @@
# Auto detect text files and perform LF normalization
* text=auto
# Explicitly set bash scripts to have unix endings
# when checked out on windows
*.sh text eol=lf
distribution/debian/* text eol=lf
macOS/Sonarr text eol=lf
*text eol=lf
# Custom for Visual Studio
*.cs diff=csharp
*.sln merge=union
*.csproj merge=union
*.vbproj merge=union
*.fsproj merge=union
*.dbproj merge=union
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain

9
.gitignore vendored
View File

@@ -45,10 +45,6 @@ _dotCover*
# DevExpress CodeRush
src/.cr/
# Emacs
*~
\#*\#
# NCrunch
*.ncrunch*
.*crunch*.local.xml
@@ -119,9 +115,7 @@ node_modules/
_output*
_rawPackage/
_dotTrace*
_tests*
_publish*
_temp*
_tests/
*.Result.xml
setup/Output/
*.~is
@@ -139,5 +133,6 @@ output/*
.DS_Store
_start
_temp_*/**/*
src/.idea/

4
.gitmodules vendored Normal file
View File

@@ -0,0 +1,4 @@
[submodule "src/ExternalModules/CurlSharp"]
path = src/ExternalModules/CurlSharp
url = https://github.com/Sonarr/CurlSharp.git
branch = master

View File

@@ -20,7 +20,7 @@ Sonarr is a PVR for Usenet and BitTorrent users. It can monitor multiple RSS fee
### Requirements
* [Visual Studio 2017](https://www.visualstudio.com/vs/)
* [Visual Studio 2017] (https://www.visualstudio.com/vs/)
* [Git](https://git-scm.com/downloads)
* [NodeJS](https://nodejs.org/en/download/)
* [Yarn](https://yarnpkg.com/)

186
build.sh
View File

@@ -1,18 +1,15 @@
#! /bin/bash
msBuildVersion='15.0'
outputFolder='./_output'
outputFolderWindows='./_output_windows'
outputFolderLinux='./_output_linux'
outputFolderMacOS='./_output_macos'
outputFolderMacOSApp='./_output_macos_app'
testPackageFolder='./_tests'
testPackageFolderWindows='./_tests_windows'
testPackageFolderLinux='./_tests_linux'
testPackageFolder='./_tests/'
testSearchPattern='*.Test/bin/x86/Release'
sourceFolder='./src'
slnFile=$sourceFolder/Sonarr.sln
updateSubFolder=Sonarr.Update
sqlitePackageDir="$HOME/.nuget/packages/system.data.sqlite.core.lidarr/1.0.111-5"
updateFolder=$outputFolder/Sonarr.Update
updateFolderMono=$outputFolderLinux/Sonarr.Update
nuget='tools/nuget/nuget.exe';
vswhere='tools/vswhere/vswhere.exe';
@@ -50,8 +47,7 @@ UpdateVersionNumber()
verBuild=`echo "${BUILD_NUMBER}" | cut -d. -f4`
BUILD_NUMBER=$verMajorMinorRevision.$verBuild
echo "##teamcity[buildNumber '$BUILD_NUMBER']"
sed -i "s/<AssemblyVersion>[0-9.*]\+<\/AssemblyVersion>/<AssemblyVersion>$BUILD_NUMBER<\/AssemblyVersion>/g" ./src/Directory.Build.props
sed -i "s/<AssemblyConfiguration>[\$()A-Za-z-]\+<\/AssemblyConfiguration>/<AssemblyConfiguration>${BRANCH:-dev}<\/AssemblyConfiguration>/g" ./src/Directory.Build.props
sed -i "s/^[[]assembly: Assembly\(File\|Informational\)\?Version[(]\"[0-9.*]\+\"[)]/[assembly: Assembly\1Version(\"$BUILD_NUMBER\")/g" ./src/NzbDrone*/Properties/AssemblyInfo.cs ./src/Sonarr*/Properties/AssemblyInfo.cs ./src/ServiceHelpers/*/Properties/AssemblyInfo.cs ./src/Common/CommonVersionInfo.cs
fi
}
@@ -90,17 +86,16 @@ CleanFolder()
BuildWithMSBuild()
{
msBuildPath=`$vswhere -latest -products \* -requires Microsoft.Component.MSBuild -find MSBuild\\\\\*\*\\\\Bin\\\\MSBuild.exe`
msBuildPath=${msBuildPath/C:\\/\/c\/}
msBuildPath=${msBuildPath//\\/\/}
msBuildDir=$(dirname "$msBuildPath")
installationPath=`$vswhere -latest -products \* -requires Microsoft.Component.MSBuild -property installationPath`
installationPath=${installationPath/C:\\/\/c\/}
installationPath=${installationPath//\\/\/}
msBuild="$installationPath/MSBuild/$msBuildVersion/Bin"
echo $msBuild
echo $msBuildDir
export PATH=$msBuildDir:$PATH
CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x64 //t:Clean //m
export PATH=$msBuild:$PATH
CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x86 //t:Clean //m
$nuget restore $slnFile
CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x64 //t:Build //m //p:AllowedReferenceRelatedFileExtensions=.pdb
CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x86 //t:Build //m //p:AllowedReferenceRelatedFileExtensions=.pdb
}
BuildWithXbuild()
@@ -108,13 +103,13 @@ BuildWithXbuild()
export MONO_IOMAP=case
CheckExitCode xbuild /t:Clean $slnFile
mono $nuget restore $slnFile
CheckExitCode xbuild /p:Configuration=Release /p:Platform=x64 /t:Build /p:AllowedReferenceRelatedFileExtensions=.pdb $slnFile
CheckExitCode xbuild /p:Configuration=Release /p:Platform=x86 /t:Build /p:AllowedReferenceRelatedFileExtensions=.pdb $slnFile
}
LintUI()
{
ProgressStart 'ESLint'
CheckExitCode yarn lint
CheckExitCode yarn eslint
ProgressEnd 'ESLint'
ProgressStart 'Stylelint'
@@ -127,7 +122,6 @@ Build()
ProgressStart 'Build'
rm -rf $outputFolder
rm -rf $testPackageFolder
if [ $runtime = "dotnet" ] ; then
BuildWithMSBuild
@@ -137,6 +131,9 @@ Build()
CleanFolder $outputFolder false
echo "Removing Mono.Posix.dll"
rm $outputFolder/Mono.Posix.dll
ProgressEnd 'Build'
}
@@ -170,36 +167,6 @@ CreateMdbs()
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()
{
ProgressStart 'Creating Mono Package'
@@ -223,11 +190,16 @@ PackageMono()
rm -f $outputFolderLinux/sqlite3.*
rm -f $outputFolderLinux/MediaInfo.*
PatchMono $outputFolderLinux
echo "Adding Sonarr.Core.dll.config (for dllmap)"
cp $sourceFolder/NzbDrone.Core/Sonarr.Core.dll.config $outputFolderLinux
echo "Adding CurlSharp.dll.config (for dllmap)"
cp $sourceFolder/NzbDrone.Common/CurlSharp.dll.config $outputFolderLinux
echo "Adding unix System.Runtime.InteropServices.RuntimeInformation.dll (for SharpRaven)"
cp $sourceFolder/packages/System.Runtime.InteropServices.RuntimeInformation.4.3.0/runtimes/unix/lib/netstandard1.1/System.Runtime.InteropServices.RuntimeInformation.dll $outputFolderLinux
cp $sourceFolder/packages/System.Runtime.InteropServices.RuntimeInformation.4.3.0/runtimes/unix/lib/netstandard1.1/System.Runtime.InteropServices.RuntimeInformation.dll $outputFolderLinux/Sonarr.Update
echo "Renaming Sonarr.Console.exe to Sonarr.exe"
rm $outputFolderLinux/Sonarr.exe*
for file in $outputFolderLinux/Sonarr.Console.exe*; do
@@ -238,7 +210,7 @@ PackageMono()
rm $outputFolderLinux/Sonarr.Windows.*
echo "Adding Sonarr.Mono to UpdatePackage"
cp $outputFolderLinux/Sonarr.Mono.* $outputFolderLinux/$updateSubFolder/
cp $outputFolderLinux/Sonarr.Mono.* $updateFolderMono
ProgressEnd 'Creating Mono Package'
}
@@ -257,11 +229,11 @@ PackageMacOS()
echo "Copying Binaries"
cp -r $outputFolderLinux/* $outputFolderMacOS
echo "Adding sqlite dylib"
cp "$sqlitePackageDir/runtimes/osx-x64/native/net46"/* $outputFolderMacOS
echo "Adding sqlite dylibs"
cp $sourceFolder/Libraries/Sqlite/*.dylib $outputFolderMacOS
echo "Adding MediaInfo dylib"
cp $sourceFolder/Libraries/MediaInfo/x64/*.dylib $outputFolderMacOS
cp $sourceFolder/Libraries/MediaInfo/*.dylib $outputFolderMacOS
ProgressEnd 'Creating MacOS Package'
}
@@ -282,11 +254,11 @@ PackageMacOSApp()
echo "Copying Binaries"
cp -r $outputFolderLinux/* $outputFolderMacOSApp/Sonarr.app/Contents/MacOS
echo "Adding sqlite dylib"
cp "$sqlitePackageDir/runtimes/osx-x64/native/net46"/* $outputFolderMacOS
echo "Adding sqlite dylibs"
cp $sourceFolder/Libraries/Sqlite/*.dylib $outputFolderMacOSApp/Sonarr.app/Contents/MacOS
echo "Adding MediaInfo dylib"
cp $sourceFolder/Libraries/MediaInfo/x64/*.dylib $outputFolderMacOS
cp $sourceFolder/Libraries/MediaInfo/*.dylib $outputFolderMacOSApp/Sonarr.app/Contents/MacOS
echo "Removing Update Folder"
rm -r $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/Sonarr.Update
@@ -294,88 +266,54 @@ PackageMacOSApp()
ProgressEnd 'Creating macOS App Package'
}
PackageTestsMono()
PackageTests()
{
ProgressStart 'Creating Mono Test Package'
ProgressStart 'Creating Test Package'
rm -rf $testPackageFolderLinux
rm -rf $testPackageFolder
mkdir $testPackageFolder
echo "Copying Binaries"
cp -r $testPackageFolder $testPackageFolderLinux
find $sourceFolder -path $testSearchPattern -exec cp -r -u -T "{}" $testPackageFolder \;
if [ $runtime = "dotnet" ] ; then
$nuget install NUnit.ConsoleRunner -Version 3.10.0 -Output $testPackageFolderLinux
$nuget install NUnit.ConsoleRunner -Version 3.2.0 -Output $testPackageFolder
else
mono $nuget install NUnit.ConsoleRunner -Version 3.10.0 -Output $testPackageFolderLinux
mono $nuget install NUnit.ConsoleRunner -Version 3.2.0 -Output $testPackageFolder
fi
echo "Creating MDBs"
CreateMdbs $testPackageFolderLinux
cp $outputFolder/*.dll $testPackageFolder
cp ./test.sh $testPackageFolder
echo "Removing PDBs"
find $testPackageFolderLinux -name "*.pdb" -exec rm "{}" \;
echo "Creating MDBs for tests"
CreateMdbs $testPackageFolder
PatchMono $testPackageFolderLinux
rm -f $testPackageFolder/*.log.config
CleanFolder $testPackageFolder true
echo "Adding Sonarr.Core.dll.config (for dllmap)"
cp $sourceFolder/NzbDrone.Core/Sonarr.Core.dll.config $testPackageFolderLinux
cp $sourceFolder/NzbDrone.Core/Sonarr.Core.dll.config $testPackageFolder
cp ./test.sh $testPackageFolderLinux/
dos2unix $testPackageFolderLinux/test.sh
echo "Adding CurlSharp.dll.config (for dllmap)"
cp $sourceFolder/NzbDrone.Common/CurlSharp.dll.config $testPackageFolder
echo "Removing Sonarr.Windows"
rm $testPackageFolderLinux/Sonarr.Windows.*
echo "Copying CurlSharp libraries"
cp $sourceFolder/ExternalModules/CurlSharp/libs/i386/* $testPackageFolder
rm -f $testPackageFolderLinux/*.log.config
CleanFolder $testPackageFolderLinux true
ProgressEnd 'Creating Linux Test Package'
ProgressEnd 'Creating Test Package'
}
PackageTestsWindows()
CleanupWindowsPackage()
{
ProgressStart 'Creating Windows Test Package'
rm -rf $testPackageFolderWindows
echo "Copying Binaries"
cp -r $testPackageFolder $testPackageFolderWindows
if [ $runtime = "dotnet" ] ; then
$nuget install NUnit.ConsoleRunner -Version 3.10.0 -Output $testPackageFolderWindows
else
mono $nuget install NUnit.ConsoleRunner -Version 3.10.0 -Output $testPackageFolderWindows
fi
cp ./test.sh $testPackageFolderWindows
ProgressStart 'Cleaning Windows Package'
echo "Removing Sonarr.Mono"
rm -f $testPackageFolderWindows/Sonarr.Mono.*
rm -f $testPackageFolderWindows/*.log.config
CleanFolder $testPackageFolderWindows true
ProgressEnd 'Creating Windows Test Package'
}
PackageWindows()
{
ProgressStart 'Creating Windows Package'
rm -rf $outputFolderWindows
echo "Copying Binaries"
cp -r $outputFolder $outputFolderWindows
echo "Removing Sonarr.Mono"
rm -f $outputFolderWindows/Sonarr.Mono.*
rm -f $outputFolder/Sonarr.Mono.*
echo "Adding Sonarr.Windows to UpdatePackage"
cp $outputFolderWindows/Sonarr.Windows.* $outputFolderWindows/$updateSubFolder/
cp $outputFolder/Sonarr.Windows.* $updateFolder
ProgressEnd 'Creating Windows Package'
ProgressEnd 'Cleaning Windows Package'
}
PublishArtifacts()
@@ -383,11 +321,10 @@ PublishArtifacts()
ProgressStart 'Publishing Artifacts'
# Tests
echo "##teamcity[publishArtifacts '$testPackageFolderWindows/** => tests.windows.zip']"
echo "##teamcity[publishArtifacts '$testPackageFolderLinux/** => tests.linux.zip']"
echo "##teamcity[publishArtifacts '_tests/** => tests.zip']"
# Releases
echo "##teamcity[publishArtifacts '$outputFolderWindows/** => Sonarr.$BRANCH.$BUILD_NUMBER.windows.zip!Sonarr']"
echo "##teamcity[publishArtifacts '$outputFolder/** => Sonarr.$BRANCH.$BUILD_NUMBER.windows.zip!Sonarr']"
echo "##teamcity[publishArtifacts '$outputFolderLinux/** => Sonarr.$BRANCH.$BUILD_NUMBER.linux.tar.gz!Sonarr']"
echo "##teamcity[publishArtifacts '$outputFolderMacOS/** => Sonarr.$BRANCH.$BUILD_NUMBER.macos.tar.gz!Sonarr']"
echo "##teamcity[publishArtifacts '$outputFolderMacOSApp/** => Sonarr.$BRANCH.$BUILD_NUMBER.macos.zip']"
@@ -417,7 +354,6 @@ RunGulp
PackageMono
PackageMacOS
PackageMacOSApp
PackageTestsMono
PackageTestsWindows
PackageWindows
PackageTests
CleanupWindowsPackage
PublishArtifacts

View File

@@ -1,7 +0,0 @@
# Note, this script is only used for local dev tests, this is not the script used for building the official sonarr package
mkdir -p /${PWD}/../_output_debian
docker build -f docker-build/Dockerfile -t sonarr-packager ./docker-build
docker run --rm -v /${PWD}/../_output_linux:/data/sonarr_bin:ro -v /${PWD}:/data/build -v /${PWD}/../_output_debian:/data/output sonarr-packager

View File

@@ -19,11 +19,7 @@ sed -i '/#BEGIN BUILTIN UPDATER/,/#END BUILTIN UPDATER/d' debian/preinst debian/
echo "# Do Not Edit\nPackageVersion=$BuildVersion\nReleaseVersion=$BuildVersion\nUpdateMethod=$PackageUpdater\nBranch=$BuildBranch" > package_info
echo Running debuild for $BuildVersion
if [ -z "${TEST_OUTPUT}" ]; then
debuild -b
else
debuild -us -uc -b
fi
debuild -b
# Restore debian directory to the original files
rm -rf ./debian
@@ -36,28 +32,16 @@ sed -i '/#BEGIN BUILTIN UPDATER/d; /#END BUILTIN UPDATER/d' debian/preinst debia
echo "# Do Not Edit\nPackageVersion=$BootstrapVersion\nReleaseVersion=$BuildVersion\nUpdateMethod=$BootstrapUpdater\nBranch=$BuildBranch" > package_info
echo Running debuild for $BootstrapVersion
if [ -z "${TEST_OUTPUT}" ]; then
debuild -b
else
debuild -us -uc -b
fi
debuild -b
echo Moving stuff around
mv ../sonarr_*.deb ./
mv ../sonarr_*.changes ./
rm ../sonarr_*.build
if [ -z "${TEST_OUTPUT}" ]; then
echo Signing Package
dpkg-sig -k 884589CE --sign builder "sonarr_${BuildVersion}_all.deb"
dpkg-sig -k 884589CE --sign builder "sonarr_${BootstrapVersion}_all.deb"
echo Signing Package
dpkg-sig -k 884589CE --sign builder "sonarr_${BuildVersion}_all.deb"
dpkg-sig -k 884589CE --sign builder "sonarr_${BootstrapVersion}_all.deb"
echo running alien
alien -r -v ./*.deb
else
echo "Exporting packages to ${TEST_OUTPUT}"
dpkg -e "sonarr_${BuildVersion}_all.deb" ${TEST_OUTPUT}/sonarr-build
dpkg -e "sonarr_${BootstrapVersion}_all.deb" ${TEST_OUTPUT}/sonarr-release
cp *.deb ${TEST_OUTPUT}/
fi
echo running alien
alien -r -v ./*.deb

View File

@@ -7,16 +7,15 @@ Vcs-Git: git@github.com:Sonarr/Sonarr.git
Vcs-Browser: https://github.com/Sonarr/Sonarr
Build-Depends: debhelper (>= 9),
dh-systemd (>= 1.5),
mono-devel (>= 5.18),
libmono-cil-dev (>= 5.18),
cli-common-dev (>= 0.9+xamarin5)
mono-devel (>= 4.6),
libmono-cil-dev (>= 4.6),
cli-common-dev (>= 0.5.7)
Package: sonarr
Architecture: all
Provides: nzbdrone
Conflicts: nzbdrone
Replaces: nzbdrone
Depends: adduser, libsqlite3-0 (>= 3.7), libmediainfo0v5 (>= 0.7.52) | libmediainfo0 (>= 0.7.52), mono-runtime (>= 5.18), ca-certificates-mono, libmono-system-net-http4.0-cil (>= 4.0.0~alpha1), ${cli:Depends}, ${misc:Depends}
Recommends: libmediainfo0v5 (>= 18.03) | libmediainfo0 (>= 18.03)
Suggests: sqlite3 (>= 3.7), mediainfo (>= 0.7.52)
Depends: adduser, libsqlite3-0 (>= 3.7), libmediainfo0v5 (>= 0.7.52), mono-runtime (>= 5.4), ${cli:Depends}, ${misc:Depends}
Recommends: sqlite3 (>= 3.7), mediainfo (>= 0.7.52), ${cli:Recommends}
Description: Internet PVR

View File

@@ -1,2 +1,2 @@
sonarr_bin/* usr/lib/sonarr/bin
package_info usr/lib/sonarr
sonarr_bin/* /usr/lib/sonarr/bin
package_info /usr/lib/sonarr

View File

@@ -104,4 +104,4 @@ fi
#DEBHELPER#
exit 0
exit 0

View File

@@ -22,25 +22,10 @@ if [ $1 = "install" ]; then
fi
if [ "$psNzbDroneUnit" != "-" ] && [ -d /run/systemd/system ]; then
if [ "$psNzbDroneUnit" = "sonarr.service" ]; then
# Conflicts with our new sonarr.service so we have to remove it
echo "NzbDrone systemd startup detected at $psNzbDroneUnit, stopping and removing..."
deb-systemd-invoke stop $psNzbDroneUnit >/dev/null
if [ -f "/etc/systemd/system/$psNzbDroneUnit" ]; then
rm /etc/systemd/system/$psNzbDroneUnit
fi
if [ -f "/usr/lib/systemd/system/$psNzbDroneUnit" ]; then
rm /usr/lib/systemd/system/$psNzbDroneUnit
fi
deb-systemd-helper purge $psNzbDroneUnit >/dev/null
deb-systemd-helper unmask $psNzbDroneUnit >/dev/null
systemctl --system daemon-reload >/dev/null || true
else
# Just disable it, so the user can revisit the settings later
echo "NzbDrone systemd startup detected at $psNzbDroneUnit, stopping and disabling..."
deb-systemd-invoke stop $psNzbDroneUnit >/dev/null
deb-systemd-invoke mask $psNzbDroneUnit >/dev/null
fi
# The user used a systemd auto-startup for NzbDrone, we can deal with that.
echo "NzbDrone systemd startup detected at $psNzbDroneUnit, stopping and disabling..."
deb-systemd-invoke stop $psNzbDroneUnit >/dev/null
deb-systemd-invoke mask $psNzbDroneUnit >/dev/null
else
# We don't support auto migration for other startup methods, so bail.
# This leaves the sonarr package in an incomplete state.

View File

@@ -1,9 +1,17 @@
#!/usr/bin/make -f
# -*- makefile -*-
# Sample debian/rules that uses debhelper.
# This file was originally written by Joey Hess and Craig Small.
# As a special exception, when this file is copied by dh-make into a
# dh-make output file, you may use that output file without restriction.
# This special exception was added by Craig Small in version 0.37 of dh-make.
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
EXCLUDE_MODULEREFS = crypt32 httpapi __Internal
# Note: System.Native is a dependency of System.Runtime.InteropServices.RuntimeInformation used by SharpRaven,
# but SharpRaven doesn't use any functions that need System.Native
EXCLUDE_MODULEREFS = crypt32 httpapi System.Native
%:
dh $@ --with=systemd --with=cli
@@ -12,7 +20,7 @@ EXCLUDE_MODULEREFS = crypt32 httpapi __Internal
override_dh_installinit:
true
# Sonarr likes debug symbols for logging
# Sonarr like debug symbols for logging
override_dh_clistrip:
override_dh_makeclilibs:

View File

@@ -1,2 +1,2 @@
ignores msbuild
ignores libmediainfo0v5
recommends libcurl3
ignores msbuild

View File

@@ -1,23 +0,0 @@
FROM ubuntu:xenial AS builder
ENV DEBIAN_FRONTEND noninteractive
ENV MONO_VERSION 5.18
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF && \
echo "deb http://download.mono-project.com/repo/debian stable-xenial/snapshots/$MONO_VERSION main" > /etc/apt/sources.list.d/mono-official-stable.list && \
apt-get update && apt-get install -y \
devscripts build-essential tofrodos \
dh-make dh-systemd \
cli-common-dev \
mono-complete \
sqlite3 libcurl3 mediainfo
RUN apt-cache policy mono-complete
RUN apt-cache policy cli-common-dev
COPY debian-start.sh /debian-start.sh
RUN fromdos /debian-start.sh
WORKDIR /data
VOLUME [ "/data/sonarr_bin", "/data/build", "/data/output" ]
CMD /debian-start.sh

View File

@@ -1,18 +0,0 @@
echo "Debian Build Dev bootstrap..."
export TEST_OUTPUT=/data/output
mkdir ${TEST_OUTPUT}
mkdir /data/temp
cp -rf /data/build/debian.sh /data/temp
cp -rf /data/build/debian /data/temp
cp -rf /data/sonarr_bin /data/temp/sonarr_bin
cd /data/temp
ls -al .
fromdos debian.sh
sh debian.sh

View File

@@ -1,22 +0,0 @@
FROM ubuntu:xenial
ENV DEBIAN_FRONTEND noninteractive
ARG MONO_VERSION=5.20
ARG MONO_URL=stable-xenial/snapshots/$MONO_VERSION
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF && \
echo "deb http://download.mono-project.com/repo/debian $MONO_URL main" > /etc/apt/sources.list.d/mono-official-stable.list && \
apt-get update && apt-get install -y \
tofrodos tzdata \
mono-complete \
sqlite3 mediainfo \
&& rm -rf /var/lib/apt/lists/*
COPY startup.sh /startup.sh
RUN fromdos /startup.sh
WORKDIR /data/
VOLUME ["/data/_tests_linux", "/data/_output_linux", "/data/_tests_results"]
CMD bash /startup.sh

View File

@@ -1,29 +0,0 @@
FROM ubuntu:xenial
ENV DEBIAN_FRONTEND noninteractive
ARG MONO_VERSION=5.20
ARG MONO_URL=stable-xenial/snapshots/$MONO_VERSION
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF && \
echo "deb http://download.mono-project.com/repo/debian $MONO_URL main" > /etc/apt/sources.list.d/mono-official-stable.list && \
apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 2009837CBFFD68F45BC180471F4F90DE2A9B4BF8 && \
echo "deb http://apt.sonarr.tv/ubuntu xenial main" > /etc/apt/sources.list.d/sonarr.list && \
apt-get update && apt-get install -y \
tofrodos tzdata \
sonarr \
sqlite3 mediainfo \
&& rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y \
libmono-system-runtime4.0-cil \
libmono-system-net-http4.0-cil \
&& rm -rf /var/lib/apt/lists/*
COPY startup.sh /startup.sh
RUN fromdos /startup.sh
WORKDIR /data/
VOLUME ["/data/_tests_linux", "/data/_output_linux", "/data/_tests_results"]
CMD bash /startup.sh

View File

@@ -1,15 +0,0 @@
echo "Preparing Test..."
mkdir -p /data/test
cp -r /data/_tests_linux/* /data/test/
cp -r /data/_output_linux /data/test/bin
cd /data/test
runTest()
{
bash test.sh Linux $1
cp TestResult.xml /data/_tests_results/TestResult_$1.xml
}
runTest Integration
runTest Unit

View File

@@ -1,121 +0,0 @@
opt_parallel=
opt_version=
opt_mode=both
while getopts 'pv:m:r?h' c
do
case $c in
p) opt_parallel=1 ;;
v) opt_version=$OPTARG ;;
m) opt_mode=$OPTARG ;;
r) opt_report=1 ;;
?|h) printf "Usage: %s [-p] [-v mono-ver] [-m sonarr|complete]\n" $0
printf " -p run parallel\n"
printf " -v run specified mono version\n"
printf " -m run only mono-'complete' or 'sonarr' package variants\n"
printf " -r only report\n"
exit 2
esac
done
# NOTE:
# each container has a 1gb tmpfs mounted since it greatly speeds up the normally intensive db operations
# make sure that the docker host has enough memory to handle about ~300 MB per container, so 2-3 GB total
# excess goes to the swap and will slow down the entire system
MONO_VERSIONS=""
# Future versions
MONO_VERSIONS="$MONO_VERSIONS 6.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.

6
frontend/browsers.js Normal file
View File

@@ -0,0 +1,6 @@
module.exports = [
'>0.25%',
'not ie 11',
'not op_mini all',
'not chrome < 60'
];

View File

@@ -5,17 +5,14 @@ const path = require('path');
const webpack = require('webpack');
const errorHandler = require('./helpers/errorHandler');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const browsers = require('../browsers');
const uiFolder = 'UI';
const frontendFolder = path.join(__dirname, '..');
const srcFolder = path.join(frontendFolder, 'src');
const isProduction = process.argv.indexOf('--production') > -1;
const distFolder = path.resolve(frontendFolder, '..', '_output', uiFolder);
console.log('Source Folder:', srcFolder);
console.log('Output Folder:', distFolder);
console.log('isProduction:', isProduction);
const cssVarsFiles = [
@@ -26,22 +23,6 @@ const cssVarsFiles = [
'../src/Styles/Variables/zIndexes'
].map(require.resolve);
// Override the way HtmlWebpackPlugin injects the scripts
HtmlWebpackPlugin.prototype.injectAssetsIntoHtml = function(html, assets, assetTags) {
const head = assetTags.head.map((v) => {
v.attributes = { rel: 'stylesheet', type: 'text/css', href: `/${v.attributes.href.replace('\\', '/')}` };
return this.createHtmlTag(v);
});
const body = assetTags.body.map((v) => {
v.attributes = { src: `/${v.attributes.src}` };
return this.createHtmlTag(v);
});
return html
.replace('<!-- webpack bundles head -->', head.join('\r\n '))
.replace('<!-- webpack bundles body -->', body.join('\r\n '));
};
const plugins = [
new webpack.DefinePlugin({
__DEV__: !isProduction,
@@ -49,12 +30,7 @@ const plugins = [
}),
new MiniCssExtractPlugin({
filename: path.join('Content', 'styles.css')
}),
new HtmlWebpackPlugin({
template: 'frontend/src/index.html',
filename: 'index.html'
filename: path.join('_output', uiFolder, 'Content', 'styles.css')
})
];
@@ -71,6 +47,8 @@ const config = {
},
entry: {
preload: 'preload.js',
vendor: 'vendor.js',
index: 'index.js'
},
@@ -86,20 +64,12 @@ const config = {
},
output: {
path: distFolder,
filename: '[name].js',
filename: path.join('_output', uiFolder, '[name].js'),
sourceMapFilename: '[file].map'
},
optimization: {
chunkIds: 'named',
splitChunks: {
chunks: 'initial'
}
},
performance: {
hints: false
chunkIds: 'named'
},
plugins,
@@ -130,7 +100,7 @@ const config = {
loose: true,
debug: false,
useBuiltIns: 'entry',
corejs: 3
targets: browsers
}
]
]
@@ -149,9 +119,8 @@ const config = {
loader: 'css-loader',
options: {
importLoaders: 1,
modules: {
localIdentName: '[name]/[local]/[hash:base64:5]'
}
localIdentName: '[name]/[local]/[hash:base64:5]',
modules: true
}
},
{
@@ -215,7 +184,7 @@ const config = {
gulp.task('webpack', () => {
return webpackStream(config)
.pipe(gulp.dest('_output/UI'));
.pipe(gulp.dest('./'));
});
gulp.task('webpackWatch', () => {
@@ -223,7 +192,7 @@ gulp.task('webpackWatch', () => {
return webpackStream(config)
.on('error', errorHandler)
.pipe(gulp.dest('_output/UI'))
.pipe(gulp.dest('./'))
.on('error', errorHandler)
.pipe(livereload())
.on('error', errorHandler);

View File

@@ -1,4 +1,5 @@
const reload = require('require-nocache')(module);
const browsers = require('./browsers');
module.exports = (ctx, configPath, options) => {
const config = {
@@ -15,7 +16,10 @@ module.exports = (ctx, configPath, options) => {
}, {})
},
'postcss-color-function': {},
'postcss-nested': {}
'postcss-nested': {},
autoprefixer: {
browsers
}
}
};

View File

@@ -1,4 +1,3 @@
.messageRemove {
.message {
margin-bottom: 30px;
color: $dangerColor;
}

View File

@@ -69,14 +69,10 @@ class RemoveQueueItemModal extends Component {
</ModalHeader>
<ModalBody>
<div>
<div className={styles.message}>
Are you sure you want to remove '{sourceTitle}' from the queue?
</div>
<div className={styles.messageRemove}>
Removing will remove the download and the file(s) from the download client.
</div>
<FormGroup>
<FormLabel>Blacklist Release</FormLabel>
<FormInputGroup

View File

@@ -35,20 +35,14 @@
.message {
margin-top: 30px;
text-align: center;
font-weight: 300;
font-size: $largeFontSize;
}
.helpText {
margin-bottom: 10px;
font-weight: 300;
font-size: 24px;
}
.noSeriesText {
margin-top: 80px;
margin-bottom: 20px;
}
.noResults {
margin-bottom: 10px;
font-weight: 300;

View File

@@ -1,7 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import { icons, kinds } from 'Helpers/Props';
import { icons } from 'Helpers/Props';
import Button from 'Components/Link/Button';
import Link from 'Components/Link/Link';
import Icon from 'Components/Icon';
@@ -79,8 +78,7 @@ class AddNewSeries extends Component {
render() {
const {
error,
items,
hasExistingSeries
items
} = this.props;
const term = this.state.term;
@@ -123,13 +121,8 @@ class AddNewSeries extends Component {
}
{
!isFetching && !!error ?
<div className={styles.message}>
<div className={styles.helpText}>
Failed to load search results, please try again.
</div>
<div>{getErrorMessage(error)}</div>
</div> : null
!isFetching && !!error &&
<div>Failed to load search results, please try again.</div>
}
{
@@ -162,34 +155,13 @@ class AddNewSeries extends Component {
}
{
term ?
null :
!term &&
<div className={styles.message}>
<div className={styles.helpText}>
It's easy to add a new series, just start typing the name the series you want to add.
</div>
<div className={styles.helpText}>It's easy to add a new series, just start typing the name the series you want to add.</div>
<div>You can also search using TVDB ID of a show. eg. tvdb:71663</div>
</div>
}
{
!term && !hasExistingSeries ?
<div className={styles.message}>
<div className={styles.noSeriesText}>
You haven't added any series yet, do you want to import some or all of your series first?
</div>
<div>
<Button
to="/add/import"
kind={kinds.PRIMARY}
>
Import Existing Series
</Button>
</div>
</div> :
null
}
<div />
</PageContentBodyConnector>
</PageContent>
@@ -204,7 +176,6 @@ AddNewSeries.propTypes = {
isAdding: PropTypes.bool.isRequired,
addError: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
hasExistingSeries: PropTypes.bool.isRequired,
onSeriesLookupChange: PropTypes.func.isRequired,
onClearSeriesLookup: PropTypes.func.isRequired
};

View File

@@ -10,15 +10,13 @@ import AddNewSeries from './AddNewSeries';
function createMapStateToProps() {
return createSelector(
(state) => state.addSeries,
(state) => state.series.items.length,
(state) => state.router.location,
(addSeries, existingSeriesCount, location) => {
(addSeries, location) => {
const { params } = parseUrl(location.search);
return {
...addSeries,
term: params.term,
hasExistingSeries: existingSeriesCount > 0
...addSeries
};
}
);

View File

@@ -66,11 +66,9 @@ class AddNewSeriesModalContent extends Component {
languageProfileId,
seriesType,
seasonFolder,
folder,
tags,
showLanguageProfile,
isSmallScreen,
isWindows,
onModalClose,
onInputChange,
...otherProps
@@ -117,15 +115,6 @@ class AddNewSeriesModalContent extends Component {
<FormInputGroup
type={inputTypes.ROOT_FOLDER_SELECT}
name="rootFolderPath"
valueOptions={{
seriesFolder: folder,
isWindows
}}
selectedValueOptions={{
seriesFolder: folder,
isWindows
}}
helpText={`'${folder}' subfolder will be created automatically`}
onChange={onInputChange}
{...rootFolderPath}
/>
@@ -271,11 +260,9 @@ AddNewSeriesModalContent.propTypes = {
languageProfileId: PropTypes.object,
seriesType: PropTypes.object.isRequired,
seasonFolder: PropTypes.object.isRequired,
folder: PropTypes.string.isRequired,
tags: PropTypes.object.isRequired,
showLanguageProfile: PropTypes.bool.isRequired,
isSmallScreen: PropTypes.bool.isRequired,
isWindows: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired,
onInputChange: PropTypes.func.isRequired,
onAddSeriesPress: PropTypes.func.isRequired

View File

@@ -4,7 +4,6 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { setAddSeriesDefault, addSeries } from 'Store/Actions/addSeriesActions';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
import selectSettings from 'Store/Selectors/selectSettings';
import AddNewSeriesModalContent from './AddNewSeriesModalContent';
@@ -13,8 +12,7 @@ function createMapStateToProps() {
(state) => state.addSeries,
(state) => state.settings.languageProfiles,
createDimensionsSelector(),
createSystemStatusSelector(),
(addSeriesState, languageProfiles, dimensions, systemStatus) => {
(addSeriesState, languageProfiles, dimensions) => {
const {
isAdding,
addError,
@@ -34,7 +32,6 @@ function createMapStateToProps() {
isSmallScreen: dimensions.isSmallScreen,
validationErrors,
validationWarnings,
isWindows: systemStatus.isWindows,
...settings
};
}

View File

@@ -1,15 +1,10 @@
.searchResult {
position: relative;
display: flex;
margin: 20px 0;
padding: 20px;
width: 100%;
color: inherit;
}
.underlay {
@add-mixin cover;
background-color: $white;
color: inherit;
transition: background 500ms;
&:hover {
@@ -19,25 +14,13 @@
}
}
.overlay {
@add-mixin linkOverlay;
position: relative;
display: flex;
}
.poster {
flex: 0 0 170px;
margin-right: 20px;
height: 250px;
}
.content {
flex: 0 1 100%;
}
.title {
display: flex;
font-weight: 300;
font-size: 36px;
}
@@ -47,18 +30,6 @@
color: $disabledColor;
}
.tvdbLink {
composes: link from '~Components/Link/Link.css';
margin-top: -4px;
margin-left: auto;
color: $textColor;
}
.tvdbLinkIcon {
margin-left: 10px;
}
.alreadyExistsIcon {
margin-left: 10px;
color: #37bc9b;

View File

@@ -24,7 +24,7 @@ class AddNewSeriesSearchResult extends Component {
componentDidUpdate(prevProps) {
if (!prevProps.isExistingSeries && this.props.isExistingSeries) {
this.onAddSeriesModalClose();
this.onAddSerisModalClose();
}
}
@@ -35,14 +35,10 @@ class AddNewSeriesSearchResult extends Component {
this.setState({ isNewAddSeriesModalOpen: true });
}
onAddSeriesModalClose = () => {
onAddSerisModalClose = () => {
this.setState({ isNewAddSeriesModalOpen: false });
}
onTVDBLinkPress = (event) => {
event.stopPropagation();
}
//
// Render
@@ -57,7 +53,6 @@ class AddNewSeriesSearchResult extends Component {
overview,
statistics,
ratings,
folder,
images,
isExistingSeries,
isSmallScreen
@@ -77,13 +72,11 @@ class AddNewSeriesSearchResult extends Component {
}
return (
<div className={styles.searchResult}>
<div>
<Link
className={styles.underlay}
className={styles.searchResult}
{...linkProps}
/>
<div className={styles.overlay}>
>
{
isSmallScreen ?
null :
@@ -95,7 +88,7 @@ class AddNewSeriesSearchResult extends Component {
/>
}
<div className={styles.content}>
<div>
<div className={styles.title}>
{title}
@@ -117,18 +110,6 @@ class AddNewSeriesSearchResult extends Component {
/> :
null
}
<Link
className={styles.tvdbLink}
to={`http://www.thetvdb.com/?tab=series&id=${tvdbId}`}
onPress={this.onTVDBLinkPress}
>
<Icon
className={styles.tvdbLinkIcon}
name={icons.EXTERNAL_LINK}
size={28}
/>
</Link>
</div>
<div>
@@ -171,7 +152,7 @@ class AddNewSeriesSearchResult extends Component {
{overview}
</div>
</div>
</div>
</Link>
<AddNewSeriesModal
isOpen={isNewAddSeriesModalOpen && !isExistingSeries}
@@ -179,9 +160,8 @@ class AddNewSeriesSearchResult extends Component {
title={title}
year={year}
overview={overview}
folder={folder}
images={images}
onModalClose={this.onAddSeriesModalClose}
onModalClose={this.onAddSerisModalClose}
/>
</div>
);
@@ -198,7 +178,6 @@ AddNewSeriesSearchResult.propTypes = {
overview: PropTypes.string,
statistics: PropTypes.object.isRequired,
ratings: PropTypes.object.isRequired,
folder: PropTypes.string.isRequired,
images: PropTypes.arrayOf(PropTypes.object).isRequired,
isExistingSeries: PropTypes.bool.isRequired,
isSmallScreen: PropTypes.bool.isRequired

View File

@@ -75,10 +75,10 @@ class ImportSeriesSelectFolder extends Component {
Some tips to ensure the import goes smoothly:
<ul>
<li className={styles.tip}>
Make sure that your files include the quality in their filenames. eg. <span className={styles.code}>episode.s02e15.bluray.mkv</span>
Make sure your files include the quality in the name. eg. <span className={styles.code}>episode.s02e15.bluray.mkv</span>
</li>
<li className={styles.tip}>
Point Sonarr to the folder containing all of your tv shows, not a specific one. eg. <span className={styles.code}>"{isWindows ? 'C:\\tv shows' : '/tv shows'}"</span> and not <span className={styles.code}>"{isWindows ? 'C:\\tv shows\\the simpsons' : '/tv shows/the simpsons'}"</span>
Point Sonarr to the folder containing all of your tv shows not a specific one. eg. <span className={styles.code}>"{isWindows ? 'C:\\tv shows' : '/tv shows'}"</span> and not <span className={styles.code}>"{isWindows ? 'C:\\tv shows\\the simpsons' : '/tv shows/the simpsons'}"</span>
</li>
</ul>
</div>

View File

@@ -2,8 +2,8 @@
flex: 1 0 14.28%;
overflow: hidden;
min-height: 70px;
border-bottom: 1px solid $calendarBorderColor;
border-left: 1px solid $calendarBorderColor;
border-bottom: 1px solid $borderColor;
border-left: 1px solid $borderColor;
}
.isSingleDay {
@@ -12,7 +12,7 @@
.dayOfMonth {
padding-right: 5px;
border-bottom: 1px solid $calendarBorderColor;
border-bottom: 1px solid $borderColor;
text-align: right;
}

View File

@@ -1,6 +1,6 @@
.days {
display: flex;
border-right: 1px solid $calendarBorderColor;
border-right: 1px solid $borderColor;
}
.day,

View File

@@ -16,13 +16,10 @@
display: flex;
}
.episodeInfo {
color: $calendarTextDim;
}
.seriesTitle,
.episodeTitle {
@add-mixin truncate;
flex: 1 0 1px;
margin-right: 10px;
}
@@ -40,10 +37,6 @@
margin-left: 3px;
}
.airTime {
color: $calendarTextDim;
}
/*
* Status
*/

View File

@@ -200,7 +200,7 @@ class CalendarEvent extends Component {
</div>
}
<div className={styles.airTime}>
<div>
{formatTime(airDateUtc, timeFormat)} - {formatTime(endTime.toISOString(), timeFormat, { includeMinuteZero: true })}
</div>
</Link>

View File

@@ -14,6 +14,7 @@
.seriesTitle {
@add-mixin truncate;
flex: 1 0 1px;
margin-right: 10px;
color: #3a3f51;
@@ -22,12 +23,10 @@
.airTime {
flex: 1 0 1px;
color: $calendarTextDim;
}
.episodeInfo {
margin-left: 10px;
color: $calendarTextDim;
}
.absoluteEpisodeNumber {
@@ -81,7 +80,3 @@
.premiere {
composes: premiere from '~Calendar/Events/CalendarEvent.css';
}
.unaired {
composes: unaired from '~Calendar/Events/CalendarEvent.css';
}

View File

@@ -56,7 +56,7 @@ class ErrorBoundary extends Component {
ErrorBoundary.propTypes = {
children: PropTypes.node.isRequired,
errorComponent: PropTypes.elementType.isRequired
errorComponent: PropTypes.func.isRequired
};
export default ErrorBoundary;

View File

@@ -144,7 +144,6 @@ class FileBrowserModalContent extends Component {
<Scroller
ref={this.setScrollerRef}
className={styles.scroller}
scrollDirection={scrollDirections.BOTH}
>
{
!!error &&
@@ -153,10 +152,7 @@ class FileBrowserModalContent extends Component {
{
isPopulated && !error &&
<Table
horizontalScroll={false}
columns={columns}
>
<Table columns={columns}>
<TableBody>
{
emptyParent &&

View File

@@ -132,7 +132,6 @@ class FilterBuilderModalContent extends Component {
filterBuilderProps,
isSaving,
saveError,
onCancelPress,
onModalClose
} = this.props;
@@ -191,7 +190,7 @@ class FilterBuilderModalContent extends Component {
</ModalBody>
<ModalFooter>
<Button onPress={onCancelPress}>
<Button onPress={onModalClose}>
Cancel
</Button>
@@ -221,7 +220,6 @@ FilterBuilderModalContent.propTypes = {
dispatchDeleteCustomFilter: PropTypes.func.isRequired,
onSaveCustomFilterPress: PropTypes.func.isRequired,
dispatchSetFilter: PropTypes.func.isRequired,
onCancelPress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};

View File

@@ -3,21 +3,16 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import sortByName from 'Utilities/Array/sortByName';
import { filterBuilderTypes } from 'Helpers/Props';
import * as filterTypes from 'Helpers/Props/filterTypes';
import FilterBuilderRowValue from './FilterBuilderRowValue';
function createTagListSelector() {
return createSelector(
(state, { filterType }) => filterType,
(state, { sectionItems }) => sectionItems,
(state, { selectedFilterBuilderProp }) => selectedFilterBuilderProp,
(filterType, sectionItems, selectedFilterBuilderProp) => {
(sectionItems, selectedFilterBuilderProp) => {
if (
(selectedFilterBuilderProp.type === filterBuilderTypes.NUMBER ||
selectedFilterBuilderProp.type === filterBuilderTypes.STRING) &&
filterType !== filterTypes.EQUAL &&
filterType !== filterBuilderTypes.NOT_EQUAL ||
!selectedFilterBuilderProp.optionsSelector
selectedFilterBuilderProp.type === filterBuilderTypes.NUMBER ||
selectedFilterBuilderProp.type === filterBuilderTypes.STRING
) {
return [];
}

View File

@@ -1,6 +1,4 @@
.tag {
height: 21px;
&.isLastTag {
.or {
display: none;

View File

@@ -29,10 +29,10 @@ function CustomFiltersModalContent(props) {
<ModalBody>
{
customFilters.map((customFilter) => {
customFilters.map((customFilter, index) => {
return (
<CustomFilter
key={customFilter.id}
key={index}
id={customFilter.id}
label={customFilter.label}
filters={customFilter.filters}

View File

@@ -34,17 +34,6 @@ class FilterModal extends Component {
});
}
onCancelPress = () => {
if (this.state.filterBuilder) {
this.setState({
filterBuilder: false,
id: null
});
} else {
this.onModalClose();
}
}
onModalClose = () => {
this.setState({
filterBuilder: false,
@@ -78,7 +67,6 @@ class FilterModal extends Component {
<FilterBuilderModalContentConnector
{...otherProps}
id={id}
onCancelPress={this.onCancelPress}
onModalClose={this.onModalClose}
/> :
<CustomFiltersModalContentConnector

View File

@@ -37,7 +37,6 @@
.suggestionsList {
margin: 5px 0;
padding-left: 0;
max-height: 200px;
list-style-type: none;
}

View File

@@ -176,7 +176,7 @@ class AutoSuggestInput extends Component {
className: classNames(
className,
hasError && styles.hasError,
hasWarning && styles.hasWarning
hasWarning && styles.hasWarning,
),
name,
value,
@@ -234,7 +234,7 @@ AutoSuggestInput.propTypes = {
minHeight: PropTypes.number.isRequired,
maxHeight: PropTypes.number.isRequired,
getSuggestionValue: PropTypes.func.isRequired,
renderInputComponent: PropTypes.elementType,
renderInputComponent: PropTypes.func,
renderSuggestion: PropTypes.func.isRequired,
onInputChange: PropTypes.func,
onInputKeyDown: PropTypes.func,

View File

@@ -2,13 +2,13 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchOptions, clearOptions } from 'Store/Actions/providerOptionActions';
import { fetchDevices, clearDevices } from 'Store/Actions/deviceActions';
import DeviceInput from './DeviceInput';
function createMapStateToProps() {
return createSelector(
(state, { value }) => value,
(state) => state.providerOptions,
(state) => state.devices,
(value, devices) => {
return {
@@ -37,8 +37,8 @@ function createMapStateToProps() {
}
const mapDispatchToProps = {
dispatchFetchOptions: fetchOptions,
dispatchClearOptions: clearOptions
dispatchFetchDevices: fetchDevices,
dispatchClearDevices: clearDevices
};
class DeviceInputConnector extends Component {
@@ -51,7 +51,7 @@ class DeviceInputConnector extends Component {
}
componentWillUnmount = () => {
this.props.dispatchClearOptions();
// this.props.dispatchClearDevices();
}
//
@@ -61,14 +61,10 @@ class DeviceInputConnector extends Component {
const {
provider,
providerData,
dispatchFetchOptions
dispatchFetchDevices
} = this.props;
dispatchFetchOptions({
action: 'getDevices',
provider,
providerData
});
dispatchFetchDevices({ provider, providerData });
}
//
@@ -96,8 +92,8 @@ DeviceInputConnector.propTypes = {
providerData: PropTypes.object.isRequired,
name: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
dispatchFetchOptions: PropTypes.func.isRequired,
dispatchClearOptions: PropTypes.func.isRequired
dispatchFetchDevices: PropTypes.func.isRequired,
dispatchClearDevices: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(DeviceInputConnector);

View File

@@ -4,7 +4,7 @@ import React, { Component } from 'react';
import { Manager, Popper, Reference } from 'react-popper';
import classNames from 'classnames';
import getUniqueElememtId from 'Utilities/getUniqueElementId';
import { isMobile as isMobileUtil } from 'Utilities/mobile';
import isMobileUtil from 'Utilities/isMobile';
import * as keyCodes from 'Utilities/Constants/keyCodes';
import { icons, sizes, scrollDirections } from 'Helpers/Props';
import Icon from 'Components/Icon';
@@ -262,7 +262,6 @@ class EnhancedSelectInput extends Component {
isDisabled,
hasError,
hasWarning,
valueOptions,
selectedValueOptions,
selectedValueComponent: SelectedValueComponent,
optionComponent: OptionComponent
@@ -364,7 +363,6 @@ class EnhancedSelectInput extends Component {
key={v.key}
id={v.key}
isSelected={index === selectedIndex}
{...valueOptions}
{...v}
isMobile={false}
onSelect={this.onSelect}
@@ -406,7 +404,6 @@ class EnhancedSelectInput extends Component {
key={v.key}
id={v.key}
isSelected={index === selectedIndex}
{...valueOptions}
{...v}
isMobile={true}
onSelect={this.onSelect}
@@ -434,10 +431,9 @@ EnhancedSelectInput.propTypes = {
isDisabled: PropTypes.bool,
hasError: PropTypes.bool,
hasWarning: PropTypes.bool,
valueOptions: PropTypes.object.isRequired,
selectedValueOptions: PropTypes.object.isRequired,
selectedValueComponent: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
optionComponent: PropTypes.elementType,
optionComponent: PropTypes.func,
onChange: PropTypes.func.isRequired
};
@@ -445,7 +441,6 @@ EnhancedSelectInput.defaultProps = {
className: styles.enhancedSelect,
disabledClassName: styles.isDisabled,
isDisabled: false,
valueOptions: {},
selectedValueOptions: {},
selectedValueComponent: HintedSelectInputSelectedValue,
optionComponent: HintedSelectInputOption

View File

@@ -18,19 +18,10 @@ class PathInput extends Component {
this._node = document.getElementById('portal-root');
this.state = {
value: props.value,
isFileBrowserModalOpen: false
};
}
componentDidUpdate(prevProps) {
const { value } = this.props;
if (prevProps.value !== value) {
this.setState({ value });
}
}
//
// Control
@@ -60,8 +51,11 @@ class PathInput extends Component {
//
// Listeners
onInputChange = ({ value }) => {
this.setState({ value });
onInputChange = (event, { newValue }) => {
this.props.onChange({
name: this.props.name,
value: newValue
});
}
onInputKeyDown = (event) => {
@@ -83,11 +77,6 @@ class PathInput extends Component {
}
onInputBlur = () => {
this.props.onChange({
name: this.props.name,
value: this.state.value
});
this.props.onClearPaths();
}
@@ -119,18 +108,13 @@ class PathInput extends Component {
const {
className,
name,
value,
paths,
includeFiles,
hasFileBrowser,
onChange,
...otherProps
} = this.props;
const {
value,
isFileBrowserModalOpen
} = this.state;
return (
<div className={className}>
<AutoSuggestInput
@@ -146,7 +130,7 @@ class PathInput extends Component {
onSuggestionSelected={this.onSuggestionSelected}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
onChange={this.onInputChange}
onChange={onChange}
/>
{
@@ -160,7 +144,7 @@ class PathInput extends Component {
</FormInputButton>
<FileBrowserModal
isOpen={isFileBrowserModalOpen}
isOpen={this.state.isFileBrowserModalOpen}
name={name}
value={value}
includeFiles={includeFiles}

View File

@@ -20,7 +20,7 @@ function getType(type) {
return inputTypes.NUMBER;
case 'path':
return inputTypes.PATH;
case 'filePath':
case 'filepath':
return inputTypes.PATH;
case 'select':
return inputTypes.SELECT;
@@ -28,7 +28,7 @@ function getType(type) {
return inputTypes.TEXT_TAG;
case 'textbox':
return inputTypes.TEXT;
case 'oAuth':
case 'oauth':
return inputTypes.OAUTH;
default:
return inputTypes.TEXT;
@@ -60,7 +60,6 @@ function ProviderFieldFormGroup(props) {
value,
type,
advanced,
hidden,
pending,
errors,
warnings,
@@ -69,13 +68,6 @@ function ProviderFieldFormGroup(props) {
...otherProps
} = props;
if (
hidden === 'hidden' ||
(hidden === 'hiddenIfNotSet' && !value)
) {
return null;
}
return (
<FormGroup
advancedSettings={advancedSettings}
@@ -94,7 +86,7 @@ function ProviderFieldFormGroup(props) {
errors={errors}
warnings={warnings}
pending={pending}
includeFiles={type === 'filePath' ? true : undefined}
includeFiles={type === 'filepath' ? true : undefined}
onChange={onChange}
{...otherProps}
/>
@@ -116,7 +108,6 @@ ProviderFieldFormGroup.propTypes = {
value: PropTypes.any,
type: PropTypes.string.isRequired,
advanced: PropTypes.bool.isRequired,
hidden: PropTypes.string,
pending: PropTypes.bool.isRequired,
errors: PropTypes.arrayOf(PropTypes.object).isRequired,
warnings: PropTypes.arrayOf(PropTypes.object).isRequired,

View File

@@ -1,3 +1,4 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
@@ -12,7 +13,7 @@ function createMapStateToProps() {
(state) => state.rootFolders,
(state, { includeNoChange }) => includeNoChange,
(rootFolders, includeNoChange) => {
const values = rootFolders.items.map((rootFolder) => {
const values = _.map(rootFolders.items, (rootFolder) => {
return {
key: rootFolder.path,
value: rootFolder.path,
@@ -84,7 +85,7 @@ class RootFolderSelectInputConnector extends Component {
onChange
} = this.props;
if (!value || !values.some((v) => v.key === value) || value === ADD_NEW_KEY) {
if (!value || !_.some(values, (v) => v.key === value) || value === ADD_NEW_KEY) {
const defaultValue = values[0];
if (defaultValue.key === ADD_NEW_KEY) {
@@ -95,27 +96,6 @@ class RootFolderSelectInputConnector extends Component {
}
}
componentDidUpdate(prevProps) {
const {
name,
value,
values,
onChange
} = this.props;
if (prevProps.values === values) {
return;
}
if (!value && values.length && values.some((v) => !!v.key && v.key !== ADD_NEW_KEY)) {
const defaultValue = values[0];
if (defaultValue.key !== ADD_NEW_KEY) {
onChange({ name, value: defaultValue.key });
}
}
}
//
// Listeners

View File

@@ -13,15 +13,6 @@
}
}
.value {
display: flex;
}
.seriesFolder {
flex: 0 0 auto;
color: $disabledColor;
}
.freeSpace {
margin-left: 15px;
color: $darkGray;

View File

@@ -7,20 +7,14 @@ import styles from './RootFolderSelectInputOption.css';
function RootFolderSelectInputOption(props) {
const {
id,
value,
freeSpace,
seriesFolder,
isMobile,
isWindows,
...otherProps
} = props;
const slashCharacter = isWindows ? '\\' : '/';
return (
<EnhancedSelectInputOption
id={id}
isMobile={isMobile}
{...otherProps}
>
@@ -29,18 +23,7 @@ function RootFolderSelectInputOption(props) {
isMobile && styles.isMobile
)}
>
<div className={styles.value}>
{value}
{
seriesFolder && id !== 'addNew' ?
<div className={styles.seriesFolder}>
{slashCharacter}
{seriesFolder}
</div> :
null
}
</div>
<div>{value}</div>
{
freeSpace != null &&
@@ -54,12 +37,9 @@ function RootFolderSelectInputOption(props) {
}
RootFolderSelectInputOption.propTypes = {
id: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
freeSpace: PropTypes.number,
seriesFolder: PropTypes.string,
isMobile: PropTypes.bool.isRequired,
isWindows: PropTypes.bool
isMobile: PropTypes.bool.isRequired
};
export default RootFolderSelectInputOption;

View File

@@ -7,20 +7,10 @@
overflow: hidden;
}
.pathContainer {
@add-mixin truncate;
display: flex;
flex: 1 0 0;
}
.path {
@add-mixin truncate;
flex: 0 1 auto;
}
.seriesFolder {
flex: 0 1 auto;
color: $disabledColor;
flex: 1 0 0;
}
.freeSpace {

View File

@@ -8,32 +8,17 @@ function RootFolderSelectInputSelectedValue(props) {
const {
value,
freeSpace,
seriesFolder,
includeFreeSpace,
isWindows,
...otherProps
} = props;
const slashCharacter = isWindows ? '\\' : '/';
return (
<EnhancedSelectInputSelectedValue
className={styles.selectedValue}
{...otherProps}
>
<div className={styles.pathContainer}>
<div className={styles.path}>
{value}
</div>
{
seriesFolder ?
<div className={styles.seriesFolder}>
{slashCharacter}
{seriesFolder}
</div> :
null
}
<div className={styles.path}>
{value}
</div>
{
@@ -49,8 +34,6 @@ function RootFolderSelectInputSelectedValue(props) {
RootFolderSelectInputSelectedValue.propTypes = {
value: PropTypes.string,
freeSpace: PropTypes.number,
seriesFolder: PropTypes.string,
isWindows: PropTypes.bool,
includeFreeSpace: PropTypes.bool.isRequired
};

View File

@@ -18,6 +18,6 @@
min-width: 20%;
max-width: 100%;
width: 0%;
height: 31px;
height: 21px;
border: none;
}

View File

@@ -226,7 +226,7 @@ class TagInput extends Component {
className={styles.internalInput}
inputContainerClassName={classNames(
inputContainerClassName,
isFocused && styles.isFocused
isFocused && styles.isFocused,
)}
value={value}
suggestions={suggestions}
@@ -260,7 +260,7 @@ TagInput.propTypes = {
minQueryLength: PropTypes.number.isRequired,
hasError: PropTypes.bool,
hasWarning: PropTypes.bool,
tagComponent: PropTypes.elementType.isRequired,
tagComponent: PropTypes.func.isRequired,
onTagAdd: PropTypes.func.isRequired,
onTagDelete: PropTypes.func.isRequired
};

View File

@@ -4,9 +4,7 @@
bottom: -1px;
left: -1px;
display: flex;
align-items: start;
flex-wrap: wrap;
padding: 1px 16px;
min-height: 33px;
padding: 6px 16px;
cursor: default;
}

View File

@@ -67,7 +67,7 @@ TagInputInput.propTypes = {
inputProps: PropTypes.object.isRequired,
kind: PropTypes.oneOf(kinds.all).isRequired,
isFocused: PropTypes.bool.isRequired,
tagComponent: PropTypes.elementType.isRequired,
tagComponent: PropTypes.func.isRequired,
onTagDelete: PropTypes.func.isRequired,
onInputContainerPress: PropTypes.func.isRequired
};

View File

@@ -1,5 +0,0 @@
.tag {
composes: link from '~Components/Link/Link.css';
height: 31px;
}

View File

@@ -4,7 +4,6 @@ import { kinds } from 'Helpers/Props';
import tagShape from 'Helpers/Props/Shapes/tagShape';
import Label from 'Components/Label';
import Link from 'Components/Link/Link';
import styles from './TagInputTag.css';
class TagInputTag extends Component {
@@ -32,9 +31,9 @@ class TagInputTag extends Component {
tag,
kind
} = this.props;
return (
<Link
className={styles.tag}
tabIndex={-1}
onPress={this.onDelete}
>

View File

@@ -95,8 +95,8 @@ FilterMenu.propTypes = {
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
buttonComponent: PropTypes.elementType.isRequired,
filterModalConnectorComponent: PropTypes.elementType,
buttonComponent: PropTypes.func.isRequired,
filterModalConnectorComponent: PropTypes.func,
filterModalConnectorComponentProps: PropTypes.object,
onFilterSelect: PropTypes.func.isRequired
};

View File

@@ -39,7 +39,6 @@ class Menu extends Component {
this._scheduleUpdate = null;
this._menuButtonId = getUniqueElememtId();
this._menuContentId = getUniqueElememtId();
this.state = {
isMenuOpen: false,
@@ -100,14 +99,12 @@ class Menu extends Component {
window.addEventListener('resize', this.onWindowResize);
window.addEventListener('scroll', this.onWindowScroll, { capture: true });
window.addEventListener('click', this.onWindowClick);
window.addEventListener('touchstart', this.onTouchStart);
}
_removeListener() {
window.removeEventListener('resize', this.onWindowResize);
window.removeEventListener('scroll', this.onWindowScroll, { capture: true });
window.removeEventListener('click', this.onWindowClick);
window.removeEventListener('touchstart', this.onTouchStart);
}
//
@@ -126,30 +123,6 @@ 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 = () => {
this.setMaxHeight();
}

View File

@@ -1,6 +1,5 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import getUniqueElementId from 'Utilities/getUniqueElementId';
import Scroller from 'Components/Scroller/Scroller';
import styles from './MenuContent.css';
@@ -13,7 +12,6 @@ class MenuContent extends Component {
const {
forwardedRef,
className,
id,
children,
style,
isOpen
@@ -21,7 +19,6 @@ class MenuContent extends Component {
return (
<div
id={id}
ref={forwardedRef}
className={className}
style={style}
@@ -41,15 +38,13 @@ class MenuContent extends Component {
MenuContent.propTypes = {
forwardedRef: PropTypes.func,
className: PropTypes.string,
id: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
style: PropTypes.object,
isOpen: PropTypes.bool
};
MenuContent.defaultProps = {
className: styles.menuContent,
id: getUniqueElementId()
className: styles.menuContent
};
export default MenuContent;

View File

@@ -1,5 +1,6 @@
.menuItem {
@add-mixin truncate;
display: block;
flex-shrink: 0;
padding: 10px 20px;
@@ -16,8 +17,3 @@
text-decoration: none;
}
}
.isDisabled {
color: $disabledColor;
pointer-events: none;
}

View File

@@ -1,6 +1,5 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import classNames from 'classnames';
import Link from 'Components/Link/Link';
import styles from './MenuItem.css';
@@ -13,17 +12,12 @@ class MenuItem extends Component {
const {
className,
children,
isDisabled,
...otherProps
} = this.props;
return (
<Link
className={classNames(
className,
isDisabled && styles.isDisabled
)}
isDisabled={isDisabled}
className={className}
{...otherProps}
>
{children}
@@ -34,13 +28,11 @@ class MenuItem extends Component {
MenuItem.propTypes = {
className: PropTypes.string,
children: PropTypes.node.isRequired,
isDisabled: PropTypes.bool.isRequired
children: PropTypes.node.isRequired
};
MenuItem.defaultProps = {
className: styles.menuItem,
isDisabled: false
className: styles.menuItem
};
export default MenuItem;

View File

@@ -29,12 +29,6 @@
overflow: hidden !important;
}
.modalOpenIOS {
position: fixed;
right: 0;
left: 0;
}
/*
* Sizes
*/

View File

@@ -4,8 +4,6 @@ import ReactDOM from 'react-dom';
import classNames from 'classnames';
import elementClass from 'element-class';
import getUniqueElememtId from 'Utilities/getUniqueElementId';
import { isIOS } from 'Utilities/mobile';
import { setScrollLock } from 'Utilities/scrollLock';
import * as keyCodes from 'Utilities/Constants/keyCodes';
import { sizes } from 'Helpers/Props';
import ErrorBoundary from 'Components/Error/ErrorBoundary';
@@ -33,7 +31,6 @@ class Modal extends Component {
this._node = document.getElementById('portal-root');
this._backgroundRef = null;
this._modalId = getUniqueElememtId();
this._bodyScrollTop = 0;
}
componentDidMount() {
@@ -72,14 +69,7 @@ class Modal extends Component {
window.addEventListener('keydown', this.onKeyDown);
if (openModals.length === 1) {
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);
}
elementClass(document.body).add(styles.modalOpen);
}
}
@@ -88,14 +78,7 @@ class Modal extends Component {
window.removeEventListener('keydown', this.onKeyDown);
if (openModals.length === 0) {
setScrollLock(false);
if (isIOS()) {
elementClass(document.body).remove(styles.modalOpenIOS);
document.body.scrollTop = this._bodyScrollTop;
} else {
elementClass(document.body).remove(styles.modalOpen);
}
elementClass(document.body).remove(styles.modalOpen);
}
}

View File

@@ -48,7 +48,7 @@ ModalBody.propTypes = {
className: PropTypes.string,
innerClassName: PropTypes.string,
children: PropTypes.node,
scrollDirection: PropTypes.oneOf(scrollDirections.all)
scrollDirection: PropTypes.oneOf([scrollDirections.NONE, scrollDirections.HORIZONTAL, scrollDirections.VERTICAL])
};
ModalBody.defaultProps = {

View File

@@ -1,6 +1,5 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { isLocked } from 'Utilities/scrollLock';
import { scrollDirections } from 'Helpers/Props';
import OverlayScroller from 'Components/Scroller/OverlayScroller';
import Scroller from 'Components/Scroller/Scroller';
@@ -8,17 +7,6 @@ import styles from './PageContentBody.css';
class PageContentBody extends Component {
//
// Listeners
onScroll = (props) => {
const { onScroll } = this.props;
if (this.props.onScroll && !isLocked()) {
onScroll(props);
}
}
//
// Render
@@ -39,7 +27,6 @@ class PageContentBody extends Component {
className={className}
scrollDirection={scrollDirections.VERTICAL}
{...otherProps}
onScroll={this.onScroll}
>
<div className={innerClassName}>
{children}
@@ -54,7 +41,6 @@ PageContentBody.propTypes = {
innerClassName: PropTypes.string,
isSmallScreen: PropTypes.bool.isRequired,
children: PropTypes.node.isRequired,
onScroll: PropTypes.func,
dispatch: PropTypes.func
};

View File

@@ -14,4 +14,4 @@ function createMapStateToProps() {
);
}
export default connect(createMapStateToProps, null, null, { forwardRef: true })(PageContentBody);
export default connect(createMapStateToProps)(PageContentBody);

View File

@@ -94,7 +94,7 @@ PageSidebarItem.propTypes = {
isActiveParent: PropTypes.bool,
isParentItem: PropTypes.bool.isRequired,
isChildItem: PropTypes.bool.isRequired,
statusComponent: PropTypes.elementType,
statusComponent: PropTypes.func,
children: PropTypes.node,
onPress: PropTypes.func
};

View File

@@ -3,7 +3,7 @@
}
.thumb {
min-height: 100px;
min-height: 50px;
border: 1px solid transparent;
border-radius: 5px;
background-color: $scrollbarBackgroundColor;

View File

@@ -4,8 +4,6 @@ import { Scrollbars } from 'react-custom-scrollbars';
import { scrollDirections } from 'Helpers/Props';
import styles from './OverlayScroller.css';
const SCROLLBAR_SIZE = 10;
class OverlayScroller extends Component {
//
@@ -23,11 +21,7 @@ class OverlayScroller extends Component {
scrollTop
} = this.props;
if (
!this._isScrolling &&
scrollTop != null &&
scrollTop !== prevProps.scrollTop
) {
if (!this._isScrolling && scrollTop != null && scrollTop !== prevProps.scrollTop) {
this._scroller.scrollTop(scrollTop);
}
}
@@ -48,42 +42,6 @@ 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) => {
return (
<div
@@ -133,8 +91,6 @@ class OverlayScroller extends Component {
ref={this._setScrollRef}
autoHide={autoHide}
hideTracksWhenNotNeeded={autoScroll}
renderTrackHorizontal={this._renderTrackHorizontal}
renderTrackVertical={this._renderTrackVertical}
renderThumbHorizontal={this._renderThumb}
renderThumbVertical={this._renderThumb}
renderView={this._renderView}

View File

@@ -2,7 +2,6 @@
@add-mixin scrollbar;
@add-mixin scrollbarTrack;
@add-mixin scrollbarThumb;
-webkit-overflow-scrolling: touch;
}
.none {
@@ -27,11 +26,3 @@
overflow-x: auto;
}
}
.both {
overflow: scroll;
&.autoScroll {
overflow: auto;
}
}

View File

@@ -66,7 +66,7 @@ class Scroller extends Component {
Scroller.propTypes = {
className: PropTypes.string,
scrollDirection: PropTypes.oneOf(scrollDirections.all).isRequired,
scrollDirection: PropTypes.oneOf([scrollDirections.NONE, scrollDirections.HORIZONTAL, scrollDirections.VERTICAL]).isRequired,
autoScroll: PropTypes.bool.isRequired,
scrollTop: PropTypes.number,
children: PropTypes.node,

View File

@@ -9,7 +9,6 @@ import titleCase from 'Utilities/String/titleCase';
import { fetchCommands, updateCommand, finishCommand } from 'Store/Actions/commandActions';
import { setAppValue, setVersion } from 'Store/Actions/appActions';
import { update, updateItem, removeItem } from 'Store/Actions/baseActions';
import { fetchSeries } from 'Store/Actions/seriesActions';
import { fetchHealth } from 'Store/Actions/systemActions';
import { fetchQueue, fetchQueueDetails } from 'Store/Actions/queueActions';
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
@@ -73,7 +72,6 @@ const mapDispatchToProps = {
dispatchFetchQueue: fetchQueue,
dispatchFetchQueueDetails: fetchQueueDetails,
dispatchFetchRootFolders: fetchRootFolders,
dispatchFetchSeries: fetchSeries,
dispatchFetchTags: fetchTags,
dispatchFetchTagDetails: fetchTagDetails
};
@@ -290,7 +288,6 @@ class SignalRConnector extends Component {
const {
dispatchFetchCommands,
dispatchFetchSeries,
dispatchSetAppValue
} = this.props;
@@ -298,7 +295,6 @@ class SignalRConnector extends Component {
// are in sync after reconnecting.
if (this.props.isReconnecting || this.props.isDisconnected) {
dispatchFetchSeries();
dispatchFetchCommands();
repopulatePage();
}
@@ -380,7 +376,6 @@ SignalRConnector.propTypes = {
dispatchFetchQueue: PropTypes.func.isRequired,
dispatchFetchQueueDetails: PropTypes.func.isRequired,
dispatchFetchRootFolders: PropTypes.func.isRequired,
dispatchFetchSeries: PropTypes.func.isRequired,
dispatchFetchTags: PropTypes.func.isRequired,
dispatchFetchTagDetails: PropTypes.func.isRequired
};

View File

@@ -53,7 +53,7 @@ RelativeDateCell.propTypes = {
shortDateFormat: PropTypes.string.isRequired,
longDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired,
component: PropTypes.elementType,
component: PropTypes.func,
dispatch: PropTypes.func
};

View File

@@ -1,7 +1,5 @@
.tableContainer {
&.horizontalScroll {
overflow-x: auto;
}
overflow-x: auto;
}
.table {
@@ -12,12 +10,7 @@
@media only screen and (max-width: $breakpointSmall) {
.tableContainer {
min-width: 100%;
width: fit-content;
&.horizontalScroll {
overflow-y: hidden;
width: 100%;
}
overflow-y: hidden;
width: 100%;
}
}

View File

@@ -1,7 +1,6 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React from 'react';
import classNames from 'classnames';
import { icons, scrollDirections } from 'Helpers/Props';
import IconButton from 'Components/Link/IconButton';
import Scroller from 'Components/Scroller/Scroller';
@@ -29,7 +28,6 @@ function getTableHeaderCellProps(props) {
function Table(props) {
const {
className,
horizontalScroll,
selectAll,
columns,
optionsComponent,
@@ -43,22 +41,14 @@ function Table(props) {
return (
<Scroller
className={classNames(
styles.tableContainer,
horizontalScroll && styles.horizontalScroll
)}
scrollDirection={
horizontalScroll ?
scrollDirections.HORIZONTAL :
scrollDirections.NONE
}
className={styles.tableContainer}
scrollDirection={scrollDirections.HORIZONTAL}
>
<table className={className}>
<TableHeader>
{
selectAll ?
<TableSelectAllHeaderCell {...otherProps} /> :
null
selectAll &&
<TableSelectAllHeaderCell {...otherProps} />
}
{
@@ -121,10 +111,9 @@ function Table(props) {
Table.propTypes = {
className: PropTypes.string,
horizontalScroll: PropTypes.bool.isRequired,
selectAll: PropTypes.bool.isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
optionsComponent: PropTypes.elementType,
optionsComponent: PropTypes.func,
pageSize: PropTypes.number,
canModifyColumns: PropTypes.bool,
children: PropTypes.node,
@@ -134,7 +123,6 @@ Table.propTypes = {
Table.defaultProps = {
className: styles.table,
horizontalScroll: true,
selectAll: false
};

View File

@@ -1,7 +1,7 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { DndProvider } from 'react-dnd';
import { DragDropContext } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import { inputTypes } from 'Helpers/Props';
import Button from 'Components/Link/Button';
@@ -127,118 +127,116 @@ class TableOptionsModal extends Component {
const isDraggingDown = isDragging && dropIndex > dragIndex;
return (
<DndProvider backend={HTML5Backend}>
<Modal
isOpen={isOpen}
onModalClose={onModalClose}
>
{
isOpen ?
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
<Modal
isOpen={isOpen}
onModalClose={onModalClose}
>
{
isOpen ?
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Table Options
</ModalHeader>
</ModalHeader>
<ModalBody>
<Form>
{
hasPageSize ?
<FormGroup>
<FormLabel>Page Size</FormLabel>
<ModalBody>
<Form>
{
hasPageSize ?
<FormGroup>
<FormLabel>Page Size</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="pageSize"
value={pageSize || 0}
helpText="Number of items to show on each page"
errors={pageSizeError ? [{ message: pageSizeError }] : undefined}
onChange={this.onPageSizeChange}
<FormInputGroup
type={inputTypes.NUMBER}
name="pageSize"
value={pageSize || 0}
helpText="Number of items to show on each page"
errors={pageSizeError ? [{ message: pageSizeError }] : undefined}
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
}
{
OptionsComponent ?
<OptionsComponent
onTableOptionChange={onTableOptionChange}
/> : null
}
{
canModifyColumns ?
<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}
/>
);
}
<div className={styles.columns}>
{
columns.map((column, index) => {
const {
name,
label,
columnLabel,
isVisible,
isModifiable
} = column;
if (isModifiable !== false) {
return (
<TableOptionsColumn
<TableOptionsColumnDragSource
key={name}
name={name}
label={label || columnLabel}
isVisible={isVisible}
isModifiable={true}
index={index}
isModifiable={false}
isDragging={isDragging}
isDraggingUp={isDraggingUp}
isDraggingDown={isDraggingDown}
onVisibleChange={this.onVisibleChange}
onColumnDragMove={this.onColumnDragMove}
onColumnDragEnd={this.onColumnDragEnd}
/>
);
})
}
}
<TableOptionsColumnDragPreview />
</div>
return (
<TableOptionsColumn
key={name}
name={name}
label={label || columnLabel}
isVisible={isVisible}
index={index}
isModifiable={false}
onVisibleChange={this.onVisibleChange}
/>
);
})
}
<TableOptionsColumnDragPreview />
</div>
</FormGroup> :
null
}
</Form>
</ModalBody>
<ModalFooter>
<Button
onPress={onModalClose}
>
</div>
</FormGroup> :
null
}
</Form>
</ModalBody>
<ModalFooter>
<Button
onPress={onModalClose}
>
Close
</Button>
</ModalFooter>
</ModalContent> :
null
}
</Modal>
</DndProvider>
</Button>
</ModalFooter>
</ModalContent> :
null
}
</Modal>
);
}
}
@@ -248,7 +246,7 @@ TableOptionsModal.propTypes = {
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
pageSize: PropTypes.number,
canModifyColumns: PropTypes.bool.isRequired,
optionsComponent: PropTypes.elementType,
optionsComponent: PropTypes.func,
onTableOptionChange: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
@@ -257,4 +255,4 @@ TableOptionsModal.defaultProps = {
canModifyColumns: true
};
export default TableOptionsModal;
export default DragDropContext(HTML5Backend)(TableOptionsModal);

View File

@@ -2,7 +2,6 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { WindowScroller } from 'react-virtualized';
import { isLocked } from 'Utilities/scrollLock';
import { scrollDirections } from 'Helpers/Props';
import Measure from 'Components/Measure';
import Scroller from 'Components/Scroller/Scroller';
@@ -52,10 +51,10 @@ class VirtualTable extends Component {
}
componentDidUpdate(prevProps, preState) {
const { scrollIndex, rowHeight } = this.props;
const scrollIndex = this.props.scrollIndex;
if (scrollIndex != null && scrollIndex !== prevProps.scrollIndex) {
const scrollTop = (scrollIndex + 1) * rowHeight + 20;
const scrollTop = (scrollIndex + 1) * ROW_HEIGHT + 20;
this.props.onScroll({ scrollTop });
}
@@ -84,16 +83,6 @@ class VirtualTable extends Component {
}
}
onScroll = (props) => {
if (isLocked()) {
return;
}
const { onScroll } = this.props;
onScroll(props);
}
//
// Render
@@ -118,7 +107,7 @@ class VirtualTable extends Component {
<Measure onMeasure={this.onMeasure}>
<WindowScroller
scrollElement={isSmallScreen ? undefined : this._contentBodyNode}
onScroll={this.onScroll}
onScroll={onScroll}
>
{({ height, isScrolling }) => {
return (
@@ -165,7 +154,6 @@ VirtualTable.propTypes = {
header: PropTypes.node.isRequired,
headerHeight: PropTypes.number.isRequired,
rowRenderer: PropTypes.func.isRequired,
rowHeight: PropTypes.number.isRequired,
onRender: PropTypes.func.isRequired,
onScroll: PropTypes.func.isRequired
};

View File

@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Manager, Popper, Reference } from 'react-popper';
import classNames from 'classnames';
import { isMobile as isMobileUtil } from 'Utilities/mobile';
import isMobileUtil from 'Utilities/isMobile';
import { kinds, tooltipPositions } from 'Helpers/Props';
import Portal from 'Components/Portal';
import styles from './Tooltip.css';

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -72,8 +72,6 @@ function createMapStateToProps() {
relativePath: episodeFile.relativePath,
language: episodeFile.language,
quality: episodeFile.quality,
languageCutoffNotMet: episodeFile.languageCutoffNotMet,
qualityCutoffNotMet: episodeFile.qualityCutoffNotMet,
...episode
};
});

View File

@@ -1,11 +1,11 @@
import PropTypes from 'prop-types';
import React from 'react';
import padNumber from 'Utilities/Number/padNumber';
import Label from 'Components/Label';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import TableRow from 'Components/Table/TableRow';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import EpisodeLanguage from 'Episode/EpisodeLanguage';
import EpisodeQuality from 'Episode/EpisodeQuality';
import styles from './EpisodeFileEditorRow';
@@ -20,8 +20,6 @@ function EpisodeFileEditorRow(props) {
airDateUtc,
language,
quality,
qualityCutoffNotMet,
languageCutoffNotMet,
isSelected,
onSelectedChange
} = props;
@@ -54,16 +52,14 @@ function EpisodeFileEditorRow(props) {
/>
<TableRowCell>
<EpisodeLanguage
language={language}
isCutoffNotMet={languageCutoffNotMet}
/>
<Label>
{language.name}
</Label>
</TableRowCell>
<TableRowCell>
<EpisodeQuality
quality={quality}
isCutoffNotMet={qualityCutoffNotMet}
/>
</TableRowCell>
</TableRow>
@@ -80,8 +76,6 @@ EpisodeFileEditorRow.propTypes = {
airDateUtc: PropTypes.string.isRequired,
language: PropTypes.object.isRequired,
quality: PropTypes.object.isRequired,
qualityCutoffNotMet: PropTypes.bool.isRequired,
languageCutoffNotMet: PropTypes.bool.isRequired,
isSelected: PropTypes.bool,
onSelectedChange: PropTypes.func.isRequired
};

View File

@@ -181,7 +181,6 @@ export const SCORE = fasUserPlus;
export const SEARCH = fasSearch;
export const SERIES_CONTINUING = fasPlay;
export const SERIES_ENDED = fasStop;
export const SERIES_DELETED = fasExclamationTriangle;
export const SETTINGS = fasCogs;
export const SHUTDOWN = fasPowerOff;
export const SORT = fasSort;

View File

@@ -1,6 +1,5 @@
export const NONE = 'none';
export const BOTH = 'both';
export const HORIZONTAL = 'horizontal';
export const VERTICAL = 'vertical';
export const all = [NONE, HORIZONTAL, VERTICAL, BOTH];
export const all = [NONE, HORIZONTAL, VERTICAL];

View File

@@ -1,32 +0,0 @@
.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;
}
}

View File

@@ -14,7 +14,6 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import SelectEpisodeRow from './SelectEpisodeRow';
import styles from './SelectEpisodeModalContent.css';
const columns = [
{
@@ -80,12 +79,10 @@ class SelectEpisodeModalContent extends Component {
render() {
const {
ids,
isFetching,
isPopulated,
error,
items,
relativePath,
sortKey,
sortDirection,
onSortPress,
@@ -100,20 +97,10 @@ class SelectEpisodeModalContent extends Component {
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 (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
<div className={styles.header}>
Manual Import - Select Episode(s)
</div>
Manual Import - Select Episode(s)
</ModalHeader>
<ModalBody>
@@ -165,28 +152,17 @@ class SelectEpisodeModalContent extends Component {
}
</ModalBody>
<ModalFooter className={styles.footer}>
<div className={styles.path}>
{
relativePath ?
relativePath :
`${selectedFilesCount} selected files`
}
</div>
<ModalFooter>
<Button onPress={onModalClose}>
Cancel
</Button>
<div className={styles.buttons}>
<Button onPress={onModalClose}>
Cancel
</Button>
<Button
kind={kinds.SUCCESS}
isDisabled={!selectionIsValid}
onPress={this.onEpisodesSelect}
>
Select Episodes
</Button>
</div>
<Button
kind={kinds.SUCCESS}
onPress={this.onEpisodesSelect}
>
Select Episodes
</Button>
</ModalFooter>
</ModalContent>
);
@@ -194,12 +170,10 @@ class SelectEpisodeModalContent extends Component {
}
SelectEpisodeModalContent.propTypes = {
ids: PropTypes.arrayOf(PropTypes.number).isRequired,
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
relativePath: PropTypes.string,
sortKey: PropTypes.string,
sortDirection: PropTypes.string,
onSortPress: PropTypes.func.isRequired,

View File

@@ -1,3 +1,4 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
@@ -55,13 +56,7 @@ class SelectEpisodeModalContentConnector extends Component {
}
onEpisodesSelect = (episodeIds) => {
const {
ids,
items,
onModalClose
} = this.props;
const selectedEpisodes = items.reduce((acc, item) => {
const episodes = _.reduce(this.props.items, (acc, item) => {
if (episodeIds.indexOf(item.id) > -1) {
acc.push(item);
}
@@ -69,22 +64,12 @@ class SelectEpisodeModalContentConnector extends Component {
return acc;
}, []);
const episodesPerFile = selectedEpisodes.length / ids.length;
const sortedEpisodes = selectedEpisodes.sort((a, b) => {
return a.seasonNumber - b.seasonNumber;
this.props.updateInteractiveImportItem({
id: this.props.id,
episodes: _.sortBy(episodes, 'episodeNumber')
});
ids.forEach((id, index) => {
const startingIndex = index * episodesPerFile;
const episodes = sortedEpisodes.slice(startingIndex, startingIndex + episodesPerFile);
this.props.updateInteractiveImportItem({
id,
episodes
});
});
onModalClose(true);
this.props.onModalClose(true);
}
//
@@ -102,7 +87,7 @@ class SelectEpisodeModalContentConnector extends Component {
}
SelectEpisodeModalContentConnector.propTypes = {
ids: PropTypes.arrayOf(PropTypes.number).isRequired,
id: PropTypes.number.isRequired,
seriesId: PropTypes.number.isRequired,
seasonNumber: PropTypes.number.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired,

View File

@@ -5,7 +5,7 @@ import getErrorMessage from 'Utilities/Object/getErrorMessage';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
import { align, icons, kinds, scrollDirections } from 'Helpers/Props';
import { align, icons, kinds } from 'Helpers/Props';
import Button from 'Components/Link/Button';
import Icon from 'Components/Icon';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
@@ -20,7 +20,6 @@ import ModalBody from 'Components/Modal/ModalBody';
import ModalFooter from 'Components/Modal/ModalFooter';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import SelectEpisodeModal from 'InteractiveImport/Episode/SelectEpisodeModal';
import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal';
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
import SelectSeriesModal from 'InteractiveImport/Series/SelectSeriesModal';
@@ -85,13 +84,12 @@ const filterExistingFilesOptions = {
const importModeOptions = [
{ key: 'move', value: 'Move Files' },
{ key: 'copy', value: 'Hardlink/Copy Files' }
{ key: 'copy', value: 'Copy Files' }
];
const SELECT = 'select';
const SERIES = 'series';
const SEASON = 'season';
const EPISODE = 'episode';
const LANGUAGE = 'language';
const QUALITY = 'quality';
@@ -210,25 +208,12 @@ class InteractiveImportModalContent extends Component {
} = this.state;
const selectedIds = this.getSelectedIds();
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 selectedItem = selectedIds.length ? _.find(items, { id: selectedIds[0] }) : null;
const errorMessage = getErrorMessage(error, 'Unable to load manual import items');
const bulkSelectOptions = [
{ key: SELECT, value: 'Select...', disabled: true },
{ key: SEASON, value: 'Select Season' },
{ key: EPISODE, value: 'Select Episode(s)' },
{ key: LANGUAGE, value: 'Select Language' },
{ key: QUALITY, value: 'Select Quality' }
];
@@ -246,7 +231,7 @@ class InteractiveImportModalContent extends Component {
Manual Import - {title || folder}
</ModalHeader>
<ModalBody scrollDirection={scrollDirections.BOTH}>
<ModalBody>
{
showFilterExistingFiles &&
<div className={styles.filterContainer}>
@@ -299,7 +284,6 @@ class InteractiveImportModalContent extends Component {
isPopulated && !!items.length && !isFetching && !isFetching &&
<Table
columns={columns}
horizontalScroll={false}
selectAll={true}
allSelected={allSelected}
allUnselected={allUnselected}
@@ -390,14 +374,6 @@ class InteractiveImportModalContent extends Component {
onModalClose={this.onSelectModalClose}
/>
<SelectEpisodeModal
isOpen={selectModalOpen === EPISODE}
ids={orderedSelectedIds}
seriesId={selectedItem && selectedItem.series && selectedItem.series.id}
seasonNumber={selectedItem && selectedItem.seasonNumber}
onModalClose={this.onSelectModalClose}
/>
<SelectLanguageModal
isOpen={selectModalOpen === LANGUAGE}
ids={selectedIds}

View File

@@ -43,7 +43,6 @@ class InteractiveImportModalContentConnector extends Component {
componentDidMount() {
const {
downloadId,
seriesId,
folder
} = this.props;
@@ -53,7 +52,6 @@ class InteractiveImportModalContentConnector extends Component {
this.props.dispatchFetchInteractiveImportItems({
downloadId,
seriesId,
folder,
filterExistingFiles
});
@@ -67,13 +65,11 @@ class InteractiveImportModalContentConnector extends Component {
if (prevState.filterExistingFiles !== filterExistingFiles) {
const {
downloadId,
seriesId,
folder
} = this.props;
this.props.dispatchFetchInteractiveImportItems({
downloadId,
seriesId,
folder,
filterExistingFiles
});
@@ -189,7 +185,6 @@ class InteractiveImportModalContentConnector extends Component {
InteractiveImportModalContentConnector.propTypes = {
downloadId: PropTypes.string,
seriesId: PropTypes.number,
folder: PropTypes.string,
filterExistingFiles: PropTypes.bool.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired,

View File

@@ -337,10 +337,9 @@ class InteractiveImportRow extends Component {
<SelectEpisodeModal
isOpen={isSelectEpisodeModalOpen}
ids={[id]}
id={id}
seriesId={series && series.id}
seasonNumber={seasonNumber}
relativePath={relativePath}
onModalClose={this.onSelectEpisodeModalClose}
/>

View File

@@ -1,21 +1,9 @@
.protocol {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 80px;
}
.title {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
word-break: break-all;
}
.indexer {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 85px;
}
.quality,
.language {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
@@ -48,9 +36,3 @@
white-space: nowrap;
}
.peers {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 75px;
}

View File

@@ -125,7 +125,7 @@ class InteractiveSearchRow extends Component {
return (
<TableRow>
<TableRowCell className={styles.protocol}>
<TableRowCell>
<ProtocolLabel
protocol={protocol}
/>
@@ -144,7 +144,7 @@ class InteractiveSearchRow extends Component {
</Link>
</TableRowCell>
<TableRowCell className={styles.indexer}>
<TableRowCell>
{indexer}
</TableRowCell>
@@ -152,7 +152,7 @@ class InteractiveSearchRow extends Component {
{formatBytes(size)}
</TableRowCell>
<TableRowCell className={styles.peers}>
<TableRowCell>
{
protocol === 'torrent' &&
<Peers

View File

@@ -1,16 +1,7 @@
.link {
composes: link from '~Components/Link/Link.css';
}
.unavailablePath {
display: flex;
align-items: center;
}
.unavailableLabel {
composes: label from '~Components/Label.css';
margin-left: 10px;
display: block;
}
.freeSpace,

View File

@@ -1,8 +1,7 @@
import PropTypes from 'prop-types';
import React from 'react';
import formatBytes from 'Utilities/Number/formatBytes';
import { icons, kinds } from 'Helpers/Props';
import Label from 'Components/Label';
import { icons } from 'Helpers/Props';
import IconButton from 'Components/Link/IconButton';
import Link from 'Components/Link/Link';
import TableRow from 'Components/Table/TableRow';
@@ -13,45 +12,30 @@ function RootFolderRow(props) {
const {
id,
path,
accessible,
freeSpace,
unmappedFolders,
onDeletePress
} = props;
const isUnavailable = !accessible;
const unmappedFoldersCount = unmappedFolders.length || '-';
return (
<TableRow>
<TableRowCell>
{
isUnavailable ?
<div className={styles.unavailablePath}>
{path}
<Label
className={styles.unavailableLabel}
kind={kinds.DANGER}
>
Unavailable
</Label>
</div> :
<Link
className={styles.link}
to={`/add/import/${id}`}
>
{path}
</Link>
}
<Link
className={styles.link}
to={`/add/import/${id}`}
>
{path}
</Link>
</TableRowCell>
<TableRowCell className={styles.freeSpace}>
{(isUnavailable || isNaN(freeSpace)) ? '-' : formatBytes(freeSpace)}
{formatBytes(freeSpace) || '-'}
</TableRowCell>
<TableRowCell className={styles.unmappedFolders}>
{isUnavailable ? '-' : unmappedFolders.length}
{unmappedFoldersCount}
</TableRowCell>
<TableRowCell className={styles.actions}>
@@ -68,13 +52,13 @@ function RootFolderRow(props) {
RootFolderRow.propTypes = {
id: PropTypes.number.isRequired,
path: PropTypes.string.isRequired,
accessible: PropTypes.bool.isRequired,
freeSpace: PropTypes.number,
freeSpace: PropTypes.number.isRequired,
unmappedFolders: PropTypes.arrayOf(PropTypes.object).isRequired,
onDeletePress: PropTypes.func.isRequired
};
RootFolderRow.defaultProps = {
freeSpace: 0,
unmappedFolders: []
};

View File

@@ -59,7 +59,6 @@ function RootFolders(props) {
key={rootFolder.id}
id={rootFolder.id}
path={rootFolder.path}
accessible={rootFolder.accessible}
freeSpace={rootFolder.freeSpace}
unmappedFolders={rootFolder.unmappedFolders}
/>

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { icons } from 'Helpers/Props';
import Icon from 'Components/Icon';
import MonitorToggleButton from 'Components/MonitorToggleButton';
import TableRow from 'Components/Table/TableRow';
@@ -7,7 +8,6 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import SeriesTitleLink from 'Series/SeriesTitleLink';
import SeasonPassSeason from './SeasonPassSeason';
import { getSeriesStatusDetails } from 'Series/SeriesStatus';
import styles from './SeasonPassRow.css';
class SeasonPassRow extends Component {
@@ -30,8 +30,6 @@ class SeasonPassRow extends Component {
onSeasonMonitoredPress
} = this.props;
const statusDetails = getSeriesStatusDetails(status);
return (
<TableRow>
<TableSelectCell
@@ -43,8 +41,8 @@ class SeasonPassRow extends Component {
<TableRowCell className={styles.status}>
<Icon
className={styles.statusIcon}
name={statusDetails.icon}
title={statusDetails.title}
name={status === 'ended' ? icons.SERIES_ENDED : icons.SERIES_CONTINUING}
title={status === 'ended' ? 'Ended' : 'Continuing'}
/>
</TableRowCell>

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