1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-04-17 21:26:22 -04:00

Compare commits

..

52 Commits

Author SHA1 Message Date
Qstick
17c6d11a47 fixup! New: Multiple Quality Profiles and Files Per Movie 2022-12-04 23:43:41 +00:00
ta264
a994502e59 Add api endpoint to generate the required login cookie
(cherry picked from commit 4180e2787a1ad5284873de4847f345b2c47df72a)
2022-12-04 23:43:41 +00:00
ta264
4154414adf Add OIDC and Plex authentication methods
(cherry picked from commit 3ff3de6b90704fba266833115cd9d03ace99aae9)
2022-12-04 23:43:41 +00:00
ta264
c5b12d074e Add explicit ApiKey requirement for ApiKey auth
(cherry picked from commit 8a3a998243e888e8f27c609f4bace5b42ad7ec50)
2022-12-04 13:06:25 -06:00
Qstick
578aa14770 Cleanup Translation Implementation in UI 2022-12-04 13:06:25 -06:00
Mark McDowall
da1bc0aa88 New: Calendar option for full color events
(cherry picked from commit 0210b5c5c1b5c56dce6f4c9f3f56366adba950d3)

Fixup Calendar for Full Color View, Final CSS fixups

Update localization
2022-12-04 13:06:25 -06:00
Qstick
a56d827744 Bump SQLite to 3.38.5 (1.0.116) 2022-12-04 13:06:25 -06:00
Qstick
a53234a107 Bump FFMpeg to 5.1.2 2022-12-04 13:06:24 -06:00
Marty Zalega
a015fa3255 Don't lowercase UrlBase in ConfigFileProvider
UrlBase should honour the case it is given.

(cherry picked from commit e1de523c89f7649e64f520b090bbdb2f56cc4b85)
2022-12-04 13:06:24 -06:00
Qstick
b2cb95829c New: Rework Movie Details view 2022-12-04 13:06:24 -06:00
Mark McDowall
64fd60a7d5 New: Migrate user passwords to Pbkdf2
(cherry picked from commit 269e72a2193b584476bec338ef41e6fb2e5cbea6)
2022-12-04 13:06:24 -06:00
Qstick
c3368d9e6c New: v4 API (DROP v3 AFTER TESTING PERIOD) 2022-12-04 13:06:20 -06:00
Qstick
583c5d501c Build Branch [REVERT] 2022-12-04 13:05:10 -06:00
Qstick
af6b16ec57 New: Multiple Quality Profiles and Files Per Movie 2022-12-04 13:05:05 -06:00
Qstick
7ac5a5843b New: Rework and Require Authentication
Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2022-12-04 13:04:33 -06:00
Qstick
2465815890 Bump Version to 5 2022-12-04 13:04:33 -06:00
Weblate
c0caf65b69 Translated using Weblate (Dutch) [skip ci]
Currently translated at 95.2% (1100 of 1155 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1155 of 1155 strings)

Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 100.0% (1155 of 1155 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Robin Flikkema <robin@robinflikkema.nl>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2022-12-02 21:13:27 -06:00
Davo1624
cd889872de Properly parse H.265 4k as 4k quality (#7812)
* Update en.json

* Properly parse H265 for 4k quality

`.WEB-DL.4K.H265.AAC` parses properly for 4k quality
`.WEB-DL.4K.H.265.AAC` parses improperly as 480p
2022-11-29 22:00:36 -06:00
Servarr
6366e335bc Automated API Docs update 2022-11-29 21:57:01 -06:00
Qstick
41f10d098e Don't block task queue for queued update task when there are longer running tasks
Fixes #7538

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
2022-11-29 21:44:03 -06:00
Qstick
b2c1698097 Fixed: False Positive HC in some cases
Fixes #7785
2022-11-29 20:52:23 -06:00
Mark McDowall
ed20487f30 Fixed: Handle Flood reporting errors for completed and stopped downloads
(cherry picked from commit f2b2eb69a3e8b271535bd92dc2f5cbfd45664daf)
2022-11-28 21:25:34 -06:00
Bruno Garcia
d1235adfc4 Sentry SDK v3.23.1
Co-authored-by: Bruno Garcia <bruno@Brunos-MacBook-Pro.local>
(cherry picked from commit de3cb07c57d762084c983336aa01b761a8e4b74a)
2022-11-28 21:30:06 +00:00
Qstick
561993e30c Fixed: Parse multiple languages for two letter cases
Fixes #7783
2022-11-25 19:16:29 -06:00
Weblate
14f8f89634 Translated using Weblate (Czech) [skip ci]
Currently translated at 90.9% (1051 of 1155 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 100.0% (1155 of 1155 strings)

Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 100.0% (1155 of 1155 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1155 of 1155 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Zalhera <tobias.bechen@gmail.com>
Co-authored-by: marapavelka <mara.pavelka@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/cs/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2022-11-24 18:40:38 -06:00
Qstick
874482dbce Webhook IndexerFlags to string list 2022-11-23 19:41:38 -06:00
Qstick
bae374c0c8 Additional Fields in Webhooks 2022-11-23 18:42:04 -06:00
Qstick
4f5ad899bb Fix Collection adding log 2022-11-23 18:31:48 -06:00
Qstick
adcd00d9fd Convert Notifiarr Payload to JSON, Standardize with Webhook 2022-11-22 21:17:25 -06:00
Servarr
d70d351ea2 Automated API Docs update 2022-11-22 19:25:54 -06:00
Qstick
ef90ac7041 Add theme property in API
Fixes #7772
2022-11-22 18:27:47 -06:00
Mark McDowall
aa8e886dab Added SECURITY.md
(cherry picked from commit 80af164385d9087a627142ca2281ae74ac0572af)

[common]
2022-11-21 19:23:43 -06:00
Mark McDowall
c7ee2c9166 Publish ApplicationStartingEvent during startup
(cherry picked from commit 5400bce1295bdc4198d2cfe0b9258bbb7ccf0852)
2022-11-21 18:47:59 -06:00
Robin Dadswell
5330815e1b fixup: some tests for log files 2022-11-21 18:42:38 -06:00
Qstick
296ec6c565 Fixup WindowsApp.cs 2022-11-20 15:08:26 -06:00
Qstick
bf89995984 Fixup SysTrayApp.cs 2022-11-20 13:09:42 -06:00
Qstick
44d7c54077 Enforce comment style in CS 2022-11-20 12:27:45 -06:00
Qstick
d37fac5343 Add PreSubstitutionRegex Capabilities
Fixes #7389
2022-11-20 12:20:50 -06:00
Qstick
ae8245c3c5 New: Reset Quality Definitions to default
(cherry picked from commit d5fff15f32fdb49768dcadd94c760678e650c884)
2022-11-20 12:20:50 -06:00
Qstick
850bfdcf82 New: Native Theme Engine
Co-Authored-By: Zak Saunders <1936903+thezak48@users.noreply.github.com>
2022-11-20 11:49:50 -06:00
Qstick
7d4865dea3 Fixed: Close all database connections on shutdown 2022-11-20 11:47:28 -06:00
Weblate
f9ef7e3578 Translated using Weblate (Dutch) [skip ci]
Currently translated at 94.7% (1088 of 1148 strings)

Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 100.0% (1148 of 1148 strings)

Translated using Weblate (Finnish) [skip ci]

Currently translated at 99.9% (1147 of 1148 strings)

Translated using Weblate (Hungarian) [skip ci]

Currently translated at 100.0% (1148 of 1148 strings)

Translated using Weblate (German) [skip ci]

Currently translated at 100.0% (1148 of 1148 strings)

Translated using Weblate (French) [skip ci]

Currently translated at 98.4% (1130 of 1148 strings)

Translated using Weblate (Portuguese (Brazil)) [skip ci]

Currently translated at 100.0% (1148 of 1148 strings)

Translated using Weblate (Catalan) [skip ci]

Currently translated at 100.0% (1148 of 1148 strings)

Co-authored-by: Csaba <csab0825@gmail.com>
Co-authored-by: Havok Dan <havokdan@yahoo.com.br>
Co-authored-by: Oskari Lavinto <olavinto@protonmail.com>
Co-authored-by: Sylvain Place <sylvain.place@etud.univ-jfc.fr>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: dtalens <databio@gmail.com>
Co-authored-by: reloxx <reloxx@interia.pl>
Co-authored-by: wauterr <wouter.rijken@gmail.com>
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/ca/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/de/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fi/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/fr/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/hu/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/nl/
Translate-URL: https://translate.servarr.com/projects/servarr/radarr/pt_BR/
Translation: Servarr/Radarr
2022-11-20 09:28:34 -06:00
Qstick
fb25422922 Fix test error due to DryIOC update 2022-11-19 16:59:53 -06:00
Mark McDowall
ac3d4bee35 Improve page scrollbar
New: Style scrollbar in Firefox
Fixed: Scrolling with click and drag

(cherry picked from commit 9bd783d49c91600d6575fc86e7bdd56858c213f1)
2022-11-19 16:31:37 -06:00
Mark McDowall
bb60510515 Added react-hooks lint rules
(cherry picked from commit 381d64259396582de8d63ada99333e42cf5e3189)
2022-11-19 16:30:16 -06:00
Qstick
5c9e11d7a0 Bump DryIoc to 5.3.0 2022-11-19 16:23:36 -06:00
Mark McDowall
6c01e8c91f Fixed: Testing SABnzbd when no categories are configured
(cherry picked from commit 0e31281828c737e3f6eecbb870960194888a970a)
2022-11-14 08:56:52 +00:00
Servarr
488a7d183e Automated API Docs update 2022-11-12 21:38:28 -06:00
Qstick
7fcb0d6e45 New: Base API info endpoint
(cherry picked from commit 5e57ffbcf9ac3a346d4bf2b692248393215bad89)
2022-11-12 19:56:41 -06:00
Jayson Reis
bd19c89f6e chore: Chain exceptions when trying to get rss's item size 2022-11-07 19:21:09 -06:00
Qstick
15e5ad5f84 Revert: Bump FFMpeg 2022-11-05 08:08:30 -05:00
Qstick
1732e23945 Fixed: Prevent Media Info error if no tags exist 2022-11-04 20:17:54 -05:00
777 changed files with 27997 additions and 3534 deletions

View File

@@ -42,7 +42,6 @@ csharp_style_var_elsewhere = true:suggestion
# Stylecop Rules # Stylecop Rules
dotnet_diagnostic.SA0001.severity = none dotnet_diagnostic.SA0001.severity = none
dotnet_diagnostic.SA1005.severity = none
dotnet_diagnostic.SA1025.severity = none dotnet_diagnostic.SA1025.severity = none
dotnet_diagnostic.SA1101.severity = none dotnet_diagnostic.SA1101.severity = none
dotnet_diagnostic.SA1116.severity = none dotnet_diagnostic.SA1116.severity = none

8
SECURITY.md Normal file
View File

@@ -0,0 +1,8 @@
# Security Policy
## Reporting a Vulnerability
Please report (suspected) security vulnerabilities on Discord (preferred) to
any of the Servarr Dev role holders (red names) or via email: development@servarr.com. You will receive a response from
us within 72 hours. If the issue is confirmed, we will release a patch as soon
as possible depending on complexity/severity.

View File

@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests' testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '4.3.1' majorVersion: '5.0.0'
minorVersion: $[counter('minorVersion', 2000)] minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)' radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)' buildName: '$(Build.SourceBranchName).$(radarrVersion)'
@@ -27,6 +27,7 @@ trigger:
include: include:
- develop - develop
- master - master
- zeus
pr: pr:
branches: branches:
@@ -1092,7 +1093,7 @@ stages:
projectVersion: '$(radarrVersion)' projectVersion: '$(radarrVersion)'
extraProperties: | extraProperties: |
sonar.exclusions=**/obj/**,**/*.dll,**/NzbDrone.Core.Test/Files/**/*,./frontend/**,**/ExternalModules/**,./src/Libraries/** sonar.exclusions=**/obj/**,**/*.dll,**/NzbDrone.Core.Test/Files/**/*,./frontend/**,**/ExternalModules/**,./src/Libraries/**
sonar.coverage.exclusions=**/Radarr.Api.V3/**/* sonar.coverage.exclusions=**/Radarr.Api.V*/**/*
sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml
sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml
- bash: | - bash: |

View File

@@ -29,7 +29,7 @@ dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p
dotnet new tool-manifest dotnet new tool-manifest
dotnet tool install --version 6.3.0 Swashbuckle.AspNetCore.Cli dotnet tool install --version 6.3.0 Swashbuckle.AspNetCore.Cli
dotnet tool run swagger tofile --output ./src/Radarr.Api.V3/openapi.json "$outputFolder/net6.0/$RUNTIME/radarr.console.dll" v3 & dotnet tool run swagger tofile --output ./src/Radarr.Api.V4/openapi.json "$outputFolder/net6.0/$RUNTIME/radarr.console.dll" v4 &
sleep 45 sleep 45

View File

@@ -39,6 +39,7 @@ module.exports = {
plugins: [ plugins: [
'filenames', 'filenames',
'react', 'react',
'react-hooks',
'simple-import-sort', 'simple-import-sort',
'import' 'import'
], ],
@@ -308,7 +309,9 @@ module.exports = {
'react/react-in-jsx-scope': 2, 'react/react-in-jsx-scope': 2,
'react/self-closing-comp': 2, 'react/self-closing-comp': 2,
'react/sort-comp': 2, 'react/sort-comp': 2,
'react/jsx-wrap-multilines': 2 'react/jsx-wrap-multilines': 2,
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'error'
}, },
overrides: [ overrides: [
{ {

View File

@@ -1,7 +1,6 @@
const reload = require('require-nocache')(module); const reload = require('require-nocache')(module);
const cssVarsFiles = [ const cssVarsFiles = [
'./src/Styles/Variables/colors',
'./src/Styles/Variables/dimensions', './src/Styles/Variables/dimensions',
'./src/Styles/Variables/fonts', './src/Styles/Variables/fonts',
'./src/Styles/Variables/animations', './src/Styles/Variables/animations',
@@ -29,4 +28,4 @@ module.exports = {
'postcss-color-function', 'postcss-color-function',
'postcss-nested' 'postcss-nested'
] ]
}; };

View File

@@ -1,13 +1,13 @@
.torrent { .torrent {
composes: label from '~Components/Label.css'; composes: label from '~Components/Label.css';
border-color: $torrentColor; border-color: var(--torrentColor);
background-color: $torrentColor; background-color: var(--torrentColor);
} }
.usenet { .usenet {
composes: label from '~Components/Label.css'; composes: label from '~Components/Label.css';
border-color: $usenetColor; border-color: var(--usenetColor);
background-color: $usenetColor; background-color: var(--usenetColor);
} }

View File

@@ -6,12 +6,12 @@
.searchIconContainer { .searchIconContainer {
width: 58px; width: 58px;
height: 46px; height: 46px;
border: 1px solid $inputBorderColor; border: 1px solid var(--inputBorderColor);
border-right: none; border-right: none;
border-radius: 4px; border-radius: 4px;
border-top-right-radius: 0; border-top-right-radius: 0;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
background-color: #edf1f2; background-color: var(--searchIconContainerBackgroundColor);
text-align: center; text-align: center;
line-height: 46px; line-height: 46px;
} }
@@ -25,7 +25,7 @@
} }
.clearLookupButton { .clearLookupButton {
border: 1px solid $inputBorderColor; border: 1px solid var(--inputBorderColor);
border-left: none; border-left: none;
border-top-right-radius: 4px; border-top-right-radius: 4px;
border-bottom-right-radius: 4px; border-bottom-right-radius: 4px;

View File

@@ -4,7 +4,7 @@
.year { .year {
margin-left: 5px; margin-left: 5px;
color: $disabledColor; color: var(--disabledColor);
} }
.poster { .poster {

View File

@@ -20,10 +20,6 @@ class AddNewMovieModalContent extends Component {
// //
// Listeners // Listeners
onQualityProfileIdChange = ({ value }) => {
this.props.onInputChange({ name: 'qualityProfileId', value: parseInt(value) });
};
onAddMoviePress = () => { onAddMoviePress = () => {
this.props.onAddMoviePress(); this.props.onAddMoviePress();
}; };
@@ -40,7 +36,7 @@ class AddNewMovieModalContent extends Component {
isAdding, isAdding,
rootFolderPath, rootFolderPath,
monitor, monitor,
qualityProfileId, qualityProfileIds,
minimumAvailability, minimumAvailability,
searchForMovie, searchForMovie,
folder, folder,
@@ -130,9 +126,9 @@ class AddNewMovieModalContent extends Component {
<FormInputGroup <FormInputGroup
type={inputTypes.QUALITY_PROFILE_SELECT} type={inputTypes.QUALITY_PROFILE_SELECT}
name="qualityProfileId" name="qualityProfileIds"
onChange={this.onQualityProfileIdChange} onChange={onInputChange}
{...qualityProfileId} {...qualityProfileIds}
/> />
</FormGroup> </FormGroup>
@@ -189,7 +185,7 @@ AddNewMovieModalContent.propTypes = {
addError: PropTypes.object, addError: PropTypes.object,
rootFolderPath: PropTypes.object, rootFolderPath: PropTypes.object,
monitor: PropTypes.object.isRequired, monitor: PropTypes.object.isRequired,
qualityProfileId: PropTypes.object, qualityProfileIds: PropTypes.arrayOf(PropTypes.object),
minimumAvailability: PropTypes.object.isRequired, minimumAvailability: PropTypes.object.isRequired,
searchForMovie: PropTypes.object.isRequired, searchForMovie: PropTypes.object.isRequired,
folder: PropTypes.string.isRequired, folder: PropTypes.string.isRequired,

View File

@@ -58,7 +58,7 @@ class AddNewMovieModalContentConnector extends Component {
tmdbId, tmdbId,
rootFolderPath, rootFolderPath,
monitor, monitor,
qualityProfileId, qualityProfileIds,
minimumAvailability, minimumAvailability,
searchForMovie, searchForMovie,
tags tags
@@ -68,7 +68,7 @@ class AddNewMovieModalContentConnector extends Component {
tmdbId, tmdbId,
rootFolderPath: rootFolderPath.value, rootFolderPath: rootFolderPath.value,
monitor: monitor.value, monitor: monitor.value,
qualityProfileId: qualityProfileId.value, qualityProfileIds: qualityProfileIds.value,
minimumAvailability: minimumAvailability.value, minimumAvailability: minimumAvailability.value,
searchForMovie: searchForMovie.value, searchForMovie: searchForMovie.value,
tags: tags.value tags: tags.value
@@ -93,7 +93,7 @@ AddNewMovieModalContentConnector.propTypes = {
tmdbId: PropTypes.number.isRequired, tmdbId: PropTypes.number.isRequired,
rootFolderPath: PropTypes.object, rootFolderPath: PropTypes.object,
monitor: PropTypes.object.isRequired, monitor: PropTypes.object.isRequired,
qualityProfileId: PropTypes.object, qualityProfileIds: PropTypes.arrayOf(PropTypes.object),
minimumAvailability: PropTypes.object.isRequired, minimumAvailability: PropTypes.object.isRequired,
searchForMovie: PropTypes.object.isRequired, searchForMovie: PropTypes.object.isRequired,
tags: PropTypes.object.isRequired, tags: PropTypes.object.isRequired,

View File

@@ -9,13 +9,15 @@
.underlay { .underlay {
@add-mixin cover; @add-mixin cover;
background-color: $white; background-color: var(--addMovieBackgroundColor);
transition: background 500ms; transition: background 500ms;
&:hover { &:hover {
background-color: #eaf2ff; background-color: var(--pageBackground);
box-shadow: 0 0 12px var(--black);
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;
transition: all 200ms ease-in;
} }
} }
@@ -31,7 +33,7 @@
display: block; display: block;
margin-right: 20px; margin-right: 20px;
height: 250px; height: 250px;
background-color: $defaultColor; background-color: var(--defaultColor);
} }
.content { .content {
@@ -56,7 +58,7 @@
.year { .year {
margin-left: 10px; margin-left: 10px;
color: $disabledColor; color: var(--disabledColor);
} }
.icons { .icons {
@@ -75,7 +77,7 @@
.exclusionIcon { .exclusionIcon {
margin-left: 10px; margin-left: 10px;
color: $dangerColor; color: var(--dangerColor);
pointer-events: all; pointer-events: all;
} }

View File

@@ -72,15 +72,19 @@ class AddNewMovieSearchResult extends Component {
colorImpairedMode, colorImpairedMode,
id, id,
monitored, monitored,
hasFile,
isAvailable, isAvailable,
queueStatus, queueStatus,
queueState, queueState,
runtime, runtime,
movieRuntimeFormat, movieRuntimeFormat,
certification certification,
statistics
} = this.props; } = this.props;
const {
movieFileCount
} = statistics;
const { const {
isNewAddMovieModalOpen isNewAddMovieModalOpen
} = this.state; } = this.state;
@@ -120,7 +124,7 @@ class AddNewMovieSearchResult extends Component {
isExistingMovie && isExistingMovie &&
<MovieIndexProgressBar <MovieIndexProgressBar
monitored={monitored} monitored={monitored}
hasFile={hasFile} hasFile={movieFileCount > 0}
status={status} status={status}
posterWidth={posterWidth} posterWidth={posterWidth}
detailedProgressBar={true} detailedProgressBar={true}
@@ -233,7 +237,7 @@ class AddNewMovieSearchResult extends Component {
{ {
isExistingMovie && isSmallScreen && isExistingMovie && isSmallScreen &&
<MovieStatusLabel <MovieStatusLabel
hasMovieFiles={hasFile} hasMovieFiles={movieFileCount > 0}
monitored={monitored} monitored={monitored}
isAvailable={isAvailable} isAvailable={isAvailable}
id={id} id={id}
@@ -290,7 +294,14 @@ AddNewMovieSearchResult.propTypes = {
queueState: PropTypes.string, queueState: PropTypes.string,
runtime: PropTypes.number.isRequired, runtime: PropTypes.number.isRequired,
movieRuntimeFormat: PropTypes.string.isRequired, movieRuntimeFormat: PropTypes.string.isRequired,
certification: PropTypes.string certification: PropTypes.string,
statistics: PropTypes.object
};
AddNewMovieSearchResult.defaultProps = {
statistics: {
movieFileCount: 0
}
}; };
export default AddNewMovieSearchResult; export default AddNewMovieSearchResult;

View File

@@ -25,13 +25,13 @@ class ImportMovieFooter extends Component {
const { const {
defaultMonitor, defaultMonitor,
defaultQualityProfileId, defaultQualityProfileIds,
defaultMinimumAvailability defaultMinimumAvailability
} = props; } = props;
this.state = { this.state = {
monitor: defaultMonitor, monitor: defaultMonitor,
qualityProfileId: defaultQualityProfileId, qualityProfileIds: defaultQualityProfileIds,
minimumAvailability: defaultMinimumAvailability minimumAvailability: defaultMinimumAvailability
}; };
} }
@@ -39,16 +39,16 @@ class ImportMovieFooter extends Component {
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
const { const {
defaultMonitor, defaultMonitor,
defaultQualityProfileId, defaultQualityProfileIds,
defaultMinimumAvailability, defaultMinimumAvailability,
isMonitorMixed, isMonitorMixed,
isQualityProfileIdMixed, isQualityProfileIdsMixed,
isMinimumAvailabilityMixed isMinimumAvailabilityMixed
} = this.props; } = this.props;
const { const {
monitor, monitor,
qualityProfileId, qualityProfileIds,
minimumAvailability minimumAvailability
} = this.state; } = this.state;
@@ -60,10 +60,10 @@ class ImportMovieFooter extends Component {
newState.monitor = defaultMonitor; newState.monitor = defaultMonitor;
} }
if (isQualityProfileIdMixed && qualityProfileId !== MIXED) { if (isQualityProfileIdsMixed && qualityProfileIds !== MIXED) {
newState.qualityProfileId = MIXED; newState.qualityProfileIds = MIXED;
} else if (!isQualityProfileIdMixed && qualityProfileId !== defaultQualityProfileId) { } else if (!isQualityProfileIdsMixed && qualityProfileIds !== defaultQualityProfileIds) {
newState.qualityProfileId = defaultQualityProfileId; newState.qualityProfileIds = defaultQualityProfileIds;
} }
if (isMinimumAvailabilityMixed && minimumAvailability !== MIXED) { if (isMinimumAvailabilityMixed && minimumAvailability !== MIXED) {
@@ -94,7 +94,7 @@ class ImportMovieFooter extends Component {
isImporting, isImporting,
isLookingUpMovie, isLookingUpMovie,
isMonitorMixed, isMonitorMixed,
isQualityProfileIdMixed, isQualityProfileIdsMixed,
isMinimumAvailabilityMixed, isMinimumAvailabilityMixed,
hasUnsearchedItems, hasUnsearchedItems,
importError, importError,
@@ -105,7 +105,7 @@ class ImportMovieFooter extends Component {
const { const {
monitor, monitor,
qualityProfileId, qualityProfileIds,
minimumAvailability minimumAvailability
} = this.state; } = this.state;
@@ -148,10 +148,10 @@ class ImportMovieFooter extends Component {
<FormInputGroup <FormInputGroup
type={inputTypes.QUALITY_PROFILE_SELECT} type={inputTypes.QUALITY_PROFILE_SELECT}
name="qualityProfileId" name="qualityProfileIds"
value={qualityProfileId} value={qualityProfileIds}
isDisabled={!selectedCount} isDisabled={!selectedCount}
includeMixed={isQualityProfileIdMixed} includeMixed={isQualityProfileIdsMixed}
onChange={this.onInputChange} onChange={this.onInputChange}
/> />
</div> </div>
@@ -251,10 +251,10 @@ ImportMovieFooter.propTypes = {
isImporting: PropTypes.bool.isRequired, isImporting: PropTypes.bool.isRequired,
isLookingUpMovie: PropTypes.bool.isRequired, isLookingUpMovie: PropTypes.bool.isRequired,
defaultMonitor: PropTypes.string.isRequired, defaultMonitor: PropTypes.string.isRequired,
defaultQualityProfileId: PropTypes.number, defaultQualityProfileIds: PropTypes.arrayOf(PropTypes.number),
defaultMinimumAvailability: PropTypes.string, defaultMinimumAvailability: PropTypes.string,
isMonitorMixed: PropTypes.bool.isRequired, isMonitorMixed: PropTypes.bool.isRequired,
isQualityProfileIdMixed: PropTypes.bool.isRequired, isQualityProfileIdsMixed: PropTypes.bool.isRequired,
isMinimumAvailabilityMixed: PropTypes.bool.isRequired, isMinimumAvailabilityMixed: PropTypes.bool.isRequired,
hasUnsearchedItems: PropTypes.bool.isRequired, hasUnsearchedItems: PropTypes.bool.isRequired,
importError: PropTypes.object, importError: PropTypes.object,

View File

@@ -18,7 +18,7 @@ function createMapStateToProps() {
(addMovie, importMovie, selectedIds) => { (addMovie, importMovie, selectedIds) => {
const { const {
monitor: defaultMonitor, monitor: defaultMonitor,
qualityProfileId: defaultQualityProfileId, qualityProfileIds: defaultQualityProfileIds,
minimumAvailability: defaultMinimumAvailability minimumAvailability: defaultMinimumAvailability
} = addMovie.defaults; } = addMovie.defaults;
@@ -30,7 +30,7 @@ function createMapStateToProps() {
} = importMovie; } = importMovie;
const isMonitorMixed = isMixed(items, selectedIds, defaultMonitor, 'monitor'); const isMonitorMixed = isMixed(items, selectedIds, defaultMonitor, 'monitor');
const isQualityProfileIdMixed = isMixed(items, selectedIds, defaultQualityProfileId, 'qualityProfileId'); const isQualityProfileIdsMixed = isMixed(items, selectedIds, defaultQualityProfileIds, 'qualityProfileIds');
const isMinimumAvailabilityMixed = isMixed(items, selectedIds, defaultMinimumAvailability, 'minimumAvailability'); const isMinimumAvailabilityMixed = isMixed(items, selectedIds, defaultMinimumAvailability, 'minimumAvailability');
const hasUnsearchedItems = !isLookingUpMovie && items.some((item) => !item.isPopulated); const hasUnsearchedItems = !isLookingUpMovie && items.some((item) => !item.isPopulated);
@@ -39,10 +39,10 @@ function createMapStateToProps() {
isLookingUpMovie, isLookingUpMovie,
isImporting, isImporting,
defaultMonitor, defaultMonitor,
defaultQualityProfileId, defaultQualityProfileIds,
defaultMinimumAvailability, defaultMinimumAvailability,
isMonitorMixed, isMonitorMixed,
isQualityProfileIdMixed, isQualityProfileIdsMixed,
isMinimumAvailabilityMixed, isMinimumAvailabilityMixed,
importError, importError,
hasUnsearchedItems hasUnsearchedItems

View File

@@ -11,7 +11,7 @@ function ImportMovieRow(props) {
const { const {
id, id,
monitor, monitor,
qualityProfileId, qualityProfileIds,
minimumAvailability, minimumAvailability,
selectedMovie, selectedMovie,
isExistingMovie, isExistingMovie,
@@ -62,8 +62,8 @@ function ImportMovieRow(props) {
<VirtualTableRowCell className={styles.qualityProfile}> <VirtualTableRowCell className={styles.qualityProfile}>
<FormInputGroup <FormInputGroup
type={inputTypes.QUALITY_PROFILE_SELECT} type={inputTypes.QUALITY_PROFILE_SELECT}
name="qualityProfileId" name="qualityProfileIds"
value={qualityProfileId} value={qualityProfileIds}
onChange={onInputChange} onChange={onInputChange}
/> />
</VirtualTableRowCell> </VirtualTableRowCell>
@@ -74,7 +74,7 @@ function ImportMovieRow(props) {
ImportMovieRow.propTypes = { ImportMovieRow.propTypes = {
id: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
monitor: PropTypes.string.isRequired, monitor: PropTypes.string.isRequired,
qualityProfileId: PropTypes.number.isRequired, qualityProfileIds: PropTypes.arrayOf(PropTypes.number).isRequired,
minimumAvailability: PropTypes.string.isRequired, minimumAvailability: PropTypes.string.isRequired,
selectedMovie: PropTypes.object, selectedMovie: PropTypes.object,
isExistingMovie: PropTypes.bool.isRequired, isExistingMovie: PropTypes.bool.isRequired,

View File

@@ -15,7 +15,7 @@ class ImportMovieTable extends Component {
const { const {
unmappedFolders, unmappedFolders,
defaultMonitor, defaultMonitor,
defaultQualityProfileId, defaultQualityProfileIds,
defaultMinimumAvailability, defaultMinimumAvailability,
onMovieLookup, onMovieLookup,
onSetImportMovieValue onSetImportMovieValue
@@ -23,7 +23,7 @@ class ImportMovieTable extends Component {
const values = { const values = {
monitor: defaultMonitor, monitor: defaultMonitor,
qualityProfileId: defaultQualityProfileId, qualityProfileIds: defaultQualityProfileIds,
minimumAvailability: defaultMinimumAvailability minimumAvailability: defaultMinimumAvailability
}; };
@@ -167,7 +167,7 @@ ImportMovieTable.propTypes = {
items: PropTypes.arrayOf(PropTypes.object), items: PropTypes.arrayOf(PropTypes.object),
unmappedFolders: PropTypes.arrayOf(PropTypes.object), unmappedFolders: PropTypes.arrayOf(PropTypes.object),
defaultMonitor: PropTypes.string.isRequired, defaultMonitor: PropTypes.string.isRequired,
defaultQualityProfileId: PropTypes.number, defaultQualityProfileIds: PropTypes.arrayOf(PropTypes.number),
defaultMinimumAvailability: PropTypes.string, defaultMinimumAvailability: PropTypes.string,
allSelected: PropTypes.bool.isRequired, allSelected: PropTypes.bool.isRequired,
allUnselected: PropTypes.bool.isRequired, allUnselected: PropTypes.bool.isRequired,

View File

@@ -13,7 +13,7 @@ function createMapStateToProps() {
(addMovie, importMovie, dimensions, allMovies) => { (addMovie, importMovie, dimensions, allMovies) => {
return { return {
defaultMonitor: addMovie.defaults.monitor, defaultMonitor: addMovie.defaults.monitor,
defaultQualityProfileId: addMovie.defaults.qualityProfileId, defaultQualityProfileIds: addMovie.defaults.qualityProfileIds,
defaultMinimumAvailability: addMovie.defaults.minimumAvailability, defaultMinimumAvailability: addMovie.defaults.minimumAvailability,
items: importMovie.items, items: importMovie.items,
isSmallScreen: dimensions.isSmallScreen, isSmallScreen: dimensions.isSmallScreen,

View File

@@ -4,7 +4,7 @@
width: 100%; width: 100%;
&:hover { &:hover {
background-color: $menuItemHoverBackgroundColor; background-color: var(--menuItemHoverBackgroundColor);
} }
} }
@@ -17,7 +17,7 @@
composes: link from '~Components/Link/Link.css'; composes: link from '~Components/Link/Link.css';
margin-left: auto; margin-left: auto;
color: $textColor; color: var(--textColor);
} }
.tmdbLinkIcon { .tmdbLinkIcon {

View File

@@ -7,10 +7,10 @@
padding: 6px 16px; padding: 6px 16px;
width: 100%; width: 100%;
height: 35px; height: 35px;
border: 1px solid $inputBorderColor; border: 1px solid var(--inputBorderColor);
border-radius: 4px; border-radius: 4px;
background-color: $white; background-color: var(--inputBackgroundColor);
box-shadow: inset 0 1px 1px $inputBoxShadowColor; box-shadow: inset 0 1px 1px var(--inputBoxShadowColor);
} }
.loading { .loading {
@@ -38,9 +38,9 @@
.content { .content {
padding: 4px; padding: 4px;
border: 1px solid $inputBorderColor; border: 1px solid var(--inputBorderColor);
border-radius: 4px; border-radius: 4px;
background-color: $white; background-color: var(--inputBackgroundColor);
} }
.searchContainer { .searchContainer {
@@ -49,12 +49,12 @@
.searchIconContainer { .searchIconContainer {
width: 58px; width: 58px;
border: 1px solid $inputBorderColor; border: 1px solid var(--inputBorderColor);
border-right: none; border-right: none;
border-radius: 4px; border-radius: 4px;
border-top-right-radius: 0; border-top-right-radius: 0;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
background-color: #edf1f2; background-color: var(--searchIconContainerBackgroundColor);
text-align: center; text-align: center;
line-height: 33px; line-height: 33px;
} }

View File

@@ -5,6 +5,7 @@ import FormInputButton from 'Components/Form/FormInputButton';
import TextInput from 'Components/Form/TextInput'; import TextInput from 'Components/Form/TextInput';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import Link from 'Components/Link/Link'; import Link from 'Components/Link/Link';
import SpinnerButton from 'Components/Link/SpinnerButton';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Portal from 'Components/Portal'; import Portal from 'Components/Portal';
import { icons, kinds } from 'Helpers/Props'; import { icons, kinds } from 'Helpers/Props';
@@ -242,7 +243,7 @@ class ImportMovieSelectMovie extends Component {
<FormInputButton <FormInputButton
kind={kinds.DEFAULT} kind={kinds.DEFAULT}
spinnerIcon={icons.REFRESH} spinnerIcon={icons.REFRESH}
canSpin={true} ButtonComponent={SpinnerButton}
isSpinning={isFetching} isSpinning={isFetching}
onPress={this.onRefreshPress} onPress={this.onRefreshPress}
> >

View File

@@ -9,7 +9,7 @@
.year { .year {
margin-left: 5px; margin-left: 5px;
color: $disabledColor; color: var(--disabledColor);
} }
.existing { .existing {

View File

@@ -4,6 +4,7 @@ import React from 'react';
import DocumentTitle from 'react-document-title'; import DocumentTitle from 'react-document-title';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import PageConnector from 'Components/Page/PageConnector'; import PageConnector from 'Components/Page/PageConnector';
import ApplyTheme from './ApplyTheme';
import AppRoutes from './AppRoutes'; import AppRoutes from './AppRoutes';
function App({ store, history }) { function App({ store, history }) {
@@ -11,9 +12,11 @@ function App({ store, history }) {
<DocumentTitle title={window.Radarr.instanceName}> <DocumentTitle title={window.Radarr.instanceName}>
<Provider store={store}> <Provider store={store}>
<ConnectedRouter history={history}> <ConnectedRouter history={history}>
<PageConnector> <ApplyTheme>
<AppRoutes app={App} /> <PageConnector>
</PageConnector> <AppRoutes app={App} />
</PageConnector>
</ApplyTheme>
</ConnectedRouter> </ConnectedRouter>
</Provider> </Provider>
</DocumentTitle> </DocumentTitle>

View File

@@ -22,7 +22,7 @@ import MediaManagementConnector from 'Settings/MediaManagement/MediaManagementCo
import MetadataSettings from 'Settings/Metadata/MetadataSettings'; import MetadataSettings from 'Settings/Metadata/MetadataSettings';
import NotificationSettings from 'Settings/Notifications/NotificationSettings'; import NotificationSettings from 'Settings/Notifications/NotificationSettings';
import Profiles from 'Settings/Profiles/Profiles'; import Profiles from 'Settings/Profiles/Profiles';
import Quality from 'Settings/Quality/Quality'; import QualityConnector from 'Settings/Quality/QualityConnector';
import Settings from 'Settings/Settings'; import Settings from 'Settings/Settings';
import TagSettings from 'Settings/Tags/TagSettings'; import TagSettings from 'Settings/Tags/TagSettings';
import UISettingsConnector from 'Settings/UI/UISettingsConnector'; import UISettingsConnector from 'Settings/UI/UISettingsConnector';
@@ -143,7 +143,7 @@ function AppRoutes(props) {
<Route <Route
path="/settings/quality" path="/settings/quality"
component={Quality} component={QualityConnector}
/> />
<Route <Route

View File

@@ -0,0 +1,49 @@
import PropTypes from 'prop-types';
import React, { Fragment, useCallback, useEffect } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import themes from 'Styles/Themes';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.ui.item.theme || window.Radarr.theme,
(
theme
) => {
return {
theme
};
}
);
}
function ApplyTheme({ theme, children }) {
// Update the CSS Variables
const updateCSSVariables = useCallback(() => {
const arrayOfVariableKeys = Object.keys(themes[theme]);
const arrayOfVariableValues = Object.values(themes[theme]);
// Loop through each array key and set the CSS Variables
arrayOfVariableKeys.forEach((cssVariableKey, index) => {
// Based on our snippet from MDN
document.documentElement.style.setProperty(
`--${cssVariableKey}`,
arrayOfVariableValues[index]
);
});
}, [theme]);
// On Component Mount and Component Update
useEffect(() => {
updateCSSVariables(theme);
}, [updateCSSVariables, theme]);
return <Fragment>{children}</Fragment>;
}
ApplyTheme.propTypes = {
theme: PropTypes.string.isRequired,
children: PropTypes.object.isRequired
};
export default connect(createMapStateToProps)(ApplyTheme);

View File

@@ -2,11 +2,11 @@
display: flex; display: flex;
overflow-x: hidden; overflow-x: hidden;
padding: 5px; padding: 5px;
border-bottom: 1px solid $borderColor; border-bottom: 1px solid var(--borderColor);
font-size: $defaultFontSize; font-size: $defaultFontSize;
&:hover { &:hover {
background-color: $tableRowHoverBackgroundColor; background-color: var(--tableRowHoverBackgroundColor);
} }
} }

View File

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

View File

@@ -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 $calendarBorderColor; border-bottom: 1px solid var(--calendarBorderColor);
border-left: 1px solid $calendarBorderColor; border-left: 1px solid var(--calendarBorderColor);
} }
.isSingleDay { .isSingleDay {
@@ -12,14 +12,14 @@
.dayOfMonth { .dayOfMonth {
padding-right: 5px; padding-right: 5px;
border-bottom: 1px solid $calendarBorderColor; border-bottom: 1px solid var(--calendarBorderColor);
text-align: right; text-align: right;
} }
.isToday { .isToday {
background-color: $calendarTodayBackgroundColor; background-color: var(--calendarTodayBackgroundColor);
} }
.isDifferentMonth { .isDifferentMonth {
color: $disabledColor; color: var(--disabledColor);
} }

View File

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

View File

@@ -1,6 +1,6 @@
.dayOfWeek { .dayOfWeek {
flex: 1 0 14.28%; flex: 1 0 14.28%;
background-color: #e4eaec; background-color: var(--calendarBackgroudColor);
text-align: center; text-align: center;
} }
@@ -9,5 +9,5 @@
} }
.isToday { .isToday {
background-color: $calendarTodayBackgroundColor; background-color: var(--calendarTodayBackgroundColor);
} }

View File

@@ -1,9 +1,11 @@
$fullColorGradient: rgba(244, 245, 246, 0.2);
.event { .event {
overflow-x: hidden; overflow-x: hidden;
margin: 4px 2px; margin: 4px 2px;
padding: 5px; padding: 5px;
border-bottom: 1px solid $borderColor; border-bottom: 1px solid var(--calendarBorderColor);
border-left: 4px solid $borderColor; border-left: 4px solid var(--calendarBorderColor);
font-size: 12px; font-size: 12px;
&:global(.colorImpaired) { &:global(.colorImpaired) {
@@ -15,10 +17,10 @@
composes: link from '~Components/Link/Link.css'; composes: link from '~Components/Link/Link.css';
display: block; display: block;
color: $defaultColor; color: var(--defaultColor);
&:hover { &:hover {
color: $defaultColor; color: var(--defaultColor);
text-decoration: none; text-decoration: none;
} }
} }
@@ -29,7 +31,7 @@
} }
.movieInfo { .movieInfo {
color: $calendarTextDim; color: var(--calendarTextDim);
} }
.movieTitle, .movieTitle,
@@ -40,7 +42,7 @@
} }
.movieTitle { .movieTitle {
color: #3a3f51; color: var(--calendarTextDimAlternate);
font-size: $defaultFontSize; font-size: $defaultFontSize;
} }
@@ -53,37 +55,85 @@
*/ */
.downloaded { .downloaded {
border-left-color: $successColor !important; border-left-color: var(--successColor) !important;
&:global(.fullColor) {
background-color: rgba(39, 194, 76, 0.4) !important;
}
&:global(.colorImpaired) { &:global(.colorImpaired) {
border-left-color: color($successColor, saturation(+15%)) !important; border-left-color: color(var(--successColor), saturation(+15%)) !important;
} }
} }
.queue { .queue {
border-left-color: $purple !important; border-left-color: var(--purple) !important;
&:global(.fullColor) {
background-color: rgba(122, 67, 182, 0.4) !important;
}
} }
.unmonitored { .unmonitored {
border-left-color: $gray !important; border-left-color: var(--gray) !important;
&:global(.fullColor) {
background-color: rgba(173, 173, 173, 0.5) !important;
}
&:global(.colorImpaired) {
background: repeating-linear-gradient(45deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px);
}
&:global(.fullColor.colorImpaired) {
background: repeating-linear-gradient(45deg, $fullColorGradient, $fullColorGradient 5px, transparent 5px, transparent 10px);
}
} }
.missingUnmonitored { .missingUnmonitored {
border-left-color: $warningColor !important; border-left-color: var(--warningColor) !important;
&:global(.fullColor) {
background-color: rgba(255, 165, 0, 0.6) !important;
}
&:global(.colorImpaired) { &:global(.colorImpaired) {
background: repeating-linear-gradient(45deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px); background: repeating-linear-gradient(45deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px);
}
&:global(.fullColor.colorImpaired) {
background: repeating-linear-gradient(45deg, $fullColorGradient, $fullColorGradient 5px, transparent 5px, transparent 10px);
} }
} }
.missingMonitored { .missingMonitored {
border-left-color: $dangerColor !important; border-left-color: var(--dangerColor) !important;
&:global(.fullColor) {
background-color: rgba(240, 80, 80, 0.6) !important;
}
&:global(.colorImpaired) { &:global(.colorImpaired) {
background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px); background: repeating-linear-gradient(90deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px);
}
&:global(.fullColor.colorImpaired) {
background: repeating-linear-gradient(90deg, $fullColorGradient, $fullColorGradient 5px, transparent 5px, transparent 10px);
} }
} }
.continuing { .unaired {
border-left-color: $primaryColor !important; border-left-color: var(--primaryColor) !important;
&:global(.fullColor) {
background-color: rgba(93, 156, 236, 0.4) !important;
}
&:global(.colorImpaired) {
background: repeating-linear-gradient(90deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px);
}
&:global(.fullColor.colorImpaired) {
background: repeating-linear-gradient(90deg, $fullColorGradient, $fullColorGradient 5px, transparent 5px, transparent 10px);
}
} }

View File

@@ -32,6 +32,7 @@ class CalendarEvent extends Component {
queueItem, queueItem,
showMovieInformation, showMovieInformation,
showCutoffUnmetIcon, showCutoffUnmetIcon,
fullColorEvents,
colorImpairedMode, colorImpairedMode,
date date
} = this.props; } = this.props;
@@ -62,7 +63,8 @@ class CalendarEvent extends Component {
styles.event, styles.event,
styles.link, styles.link,
styles[statusStyle], styles[statusStyle],
colorImpairedMode && 'colorImpaired' colorImpairedMode && 'colorImpaired',
fullColorEvents && 'fullColor'
)} )}
// component="div" // component="div"
to={link} to={link}
@@ -97,7 +99,7 @@ class CalendarEvent extends Component {
<Icon <Icon
className={styles.statusIcon} className={styles.statusIcon}
name={icons.MOVIE_FILE} name={icons.MOVIE_FILE}
kind={kinds.WARNING} kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
title={translate('QualityCutoffHasNotBeenMet')} title={translate('QualityCutoffHasNotBeenMet')}
/> />
} }
@@ -142,11 +144,12 @@ CalendarEvent.propTypes = {
digitalRelease: PropTypes.string, digitalRelease: PropTypes.string,
monitored: PropTypes.bool.isRequired, monitored: PropTypes.bool.isRequired,
certification: PropTypes.string, certification: PropTypes.string,
hasFile: PropTypes.bool.isRequired, hasFile: PropTypes.bool,
grabbed: PropTypes.bool, grabbed: PropTypes.bool,
queueItem: PropTypes.object, queueItem: PropTypes.object,
showMovieInformation: PropTypes.bool.isRequired, showMovieInformation: PropTypes.bool.isRequired,
showCutoffUnmetIcon: PropTypes.bool.isRequired, showCutoffUnmetIcon: PropTypes.bool.isRequired,
fullColorEvents: PropTypes.bool.isRequired,
timeFormat: PropTypes.string.isRequired, timeFormat: PropTypes.string.isRequired,
colorImpairedMode: PropTypes.bool.isRequired, colorImpairedMode: PropTypes.bool.isRequired,
date: PropTypes.string.isRequired date: PropTypes.string.isRequired

View File

@@ -2,7 +2,6 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import QueueDetails from 'Activity/Queue/QueueDetails'; import QueueDetails from 'Activity/Queue/QueueDetails';
import CircularProgressBar from 'Components/CircularProgressBar'; import CircularProgressBar from 'Components/CircularProgressBar';
import colors from 'Styles/Variables/colors';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
function CalendarEventQueueDetails(props) { function CalendarEventQueueDetails(props) {
@@ -35,7 +34,7 @@ function CalendarEventQueueDetails(props) {
progress={progress} progress={progress}
size={20} size={20}
strokeWidth={2} strokeWidth={2}
strokeColor={colors.purple} strokeColor={'#7a43b6'}
/> />
</div> </div>
} }

View File

@@ -9,6 +9,7 @@ import styles from './Legend.css';
function Legend(props) { function Legend(props) {
const { const {
showCutoffUnmetIcon, showCutoffUnmetIcon,
fullColorEvents,
colorImpairedMode colorImpairedMode
} = props; } = props;
@@ -19,7 +20,7 @@ function Legend(props) {
<LegendIconItem <LegendIconItem
name={translate('CutoffUnmet')} name={translate('CutoffUnmet')}
icon={icons.MOVIE_FILE} icon={icons.MOVIE_FILE}
kind={kinds.WARNING} kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
tooltip={translate('QualityOrLangCutoffHasNotBeenMet')} tooltip={translate('QualityOrLangCutoffHasNotBeenMet')}
/> />
); );
@@ -31,12 +32,14 @@ function Legend(props) {
<LegendItem <LegendItem
style='ended' style='ended'
name={translate('DownloadedAndMonitored')} name={translate('DownloadedAndMonitored')}
fullColorEvents={fullColorEvents}
colorImpairedMode={colorImpairedMode} colorImpairedMode={colorImpairedMode}
/> />
<LegendItem <LegendItem
style='availNotMonitored' style='availNotMonitored'
name={translate('DownloadedButNotMonitored')} name={translate('DownloadedButNotMonitored')}
fullColorEvents={fullColorEvents}
colorImpairedMode={colorImpairedMode} colorImpairedMode={colorImpairedMode}
/> />
</div> </div>
@@ -45,12 +48,14 @@ function Legend(props) {
<LegendItem <LegendItem
style='missingMonitored' style='missingMonitored'
name={translate('MissingMonitoredAndConsideredAvailable')} name={translate('MissingMonitoredAndConsideredAvailable')}
fullColorEvents={fullColorEvents}
colorImpairedMode={colorImpairedMode} colorImpairedMode={colorImpairedMode}
/> />
<LegendItem <LegendItem
style='missingUnmonitored' style='missingUnmonitored'
name={translate('MissingNotMonitored')} name={translate('MissingNotMonitored')}
fullColorEvents={fullColorEvents}
colorImpairedMode={colorImpairedMode} colorImpairedMode={colorImpairedMode}
/> />
</div> </div>
@@ -59,12 +64,14 @@ function Legend(props) {
<LegendItem <LegendItem
style='queue' style='queue'
name={translate('Queued')} name={translate('Queued')}
fullColorEvents={fullColorEvents}
colorImpairedMode={colorImpairedMode} colorImpairedMode={colorImpairedMode}
/> />
<LegendItem <LegendItem
style='continuing' style='continuing'
name={translate('Unreleased')} name={translate('Unreleased')}
fullColorEvents={fullColorEvents}
colorImpairedMode={colorImpairedMode} colorImpairedMode={colorImpairedMode}
/> />
</div> </div>
@@ -79,7 +86,9 @@ function Legend(props) {
} }
Legend.propTypes = { Legend.propTypes = {
view: PropTypes.string.isRequired,
showCutoffUnmetIcon: PropTypes.bool.isRequired, showCutoffUnmetIcon: PropTypes.bool.isRequired,
fullColorEvents: PropTypes.bool.isRequired,
colorImpairedMode: PropTypes.bool.isRequired colorImpairedMode: PropTypes.bool.isRequired
}; };

View File

@@ -6,10 +6,12 @@ import Legend from './Legend';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
(state) => state.calendar.options, (state) => state.calendar.options,
(state) => state.calendar.view,
createUISettingsSelector(), createUISettingsSelector(),
(calendarOptions, uiSettings) => { (calendarOptions, view, uiSettings) => {
return { return {
...calendarOptions, ...calendarOptions,
view,
colorImpairedMode: uiSettings.enableColorImpairedMode colorImpairedMode: uiSettings.enableColorImpairedMode
}; };
} }

View File

@@ -8,6 +8,7 @@ function LegendIconItem(props) {
name, name,
icon, icon,
kind, kind,
darken,
tooltip tooltip
} = props; } = props;
@@ -19,6 +20,7 @@ function LegendIconItem(props) {
<Icon <Icon
className={styles.icon} className={styles.icon}
name={icon} name={icon}
darken={darken}
kind={kind} kind={kind}
/> />
@@ -31,7 +33,12 @@ LegendIconItem.propTypes = {
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
icon: PropTypes.object.isRequired, icon: PropTypes.object.isRequired,
kind: PropTypes.string.isRequired, kind: PropTypes.string.isRequired,
darken: PropTypes.bool.isRequired,
tooltip: PropTypes.string.isRequired tooltip: PropTypes.string.isRequired
}; };
LegendIconItem.defaultProps = {
darken: false
};
export default LegendIconItem; export default LegendIconItem;

View File

@@ -1,3 +1,5 @@
$fullColorGradient: rgba(244, 245, 246, 0.2);
.legendItemContainer { .legendItemContainer {
margin-right: 5px; margin-right: 5px;
width: 220px; width: 220px;
@@ -20,53 +22,55 @@
.queue { .queue {
composes: legendItemColor; composes: legendItemColor;
background-color: $queueColor; background-color: var(--queueColor);
} }
.continuing { .continuing {
composes: legendItemColor; composes: legendItemColor;
background-color: $primaryColor; background-color: var(--primaryColor);
} }
.availNotMonitored { .availNotMonitored {
composes: legendItemColor; composes: legendItemColor;
background-color: $darkGray; background-color: var(--darkGray);
} }
.ended { .ended {
composes: legendItemColor; composes: legendItemColor;
background-color: $successColor; background-color: var(--successColor);
} }
.missingMonitored { .missingMonitored {
composes: legendItemColor; composes: legendItemColor;
background-color: $dangerColor; background-color: var(--dangerColor);
&:global(.colorImpaired) { &:global(.colorImpaired) {
background: repeating-linear-gradient(90deg, color($dangerColor shade(5%)), color($dangerColor shade(5%)) 5px, color($dangerColor shade(15%)) 5px, color($dangerColor shade(15%)) 10px); background: repeating-linear-gradient(90deg, color(var(--dangerColor) shade(5%)), color(var(--dangerColor) shade(5%)) 5px, color(var(--dangerColor) shade(15%)) 5px, color(var(--dangerColor) shade(15%)) 10px);
} }
} }
.missingUnmonitored { .missingUnmonitored {
composes: legendItemColor; composes: legendItemColor;
background-color: $warningColor; background-color: var(--warningColor);
&:global(.colorImpaired) { &:global(.colorImpaired) {
background: repeating-linear-gradient(45deg, $warningColor, $warningColor 5px, color($warningColor tint(15%)) 5px, color($warningColor tint(15%)) 10px); background: repeating-linear-gradient(45deg, var(--warningColor), var(--warningColor) 5px, color(var(--warningColor) tint(15%)) 5px, color(var(--warningColor) tint(15%)) 10px);
} }
} }
.missingMonitoredColorImpaired { .missingMonitoredColorImpaired {
background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px); background: repeating-linear-gradient(90deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px);
color: var(--white);
} }
.missingUnmonitoredColorImpaired { .missingUnmonitoredColorImpaired {
background: repeating-linear-gradient(45deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px); background: repeating-linear-gradient(45deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px);
color: var(--white);
} }
.legendItemText { .legendItemText {

View File

@@ -7,6 +7,7 @@ function LegendItem(props) {
const { const {
name, name,
style, style,
fullColorEvents,
colorImpairedMode colorImpairedMode
} = props; } = props;
@@ -16,7 +17,8 @@ function LegendItem(props) {
className={classNames( className={classNames(
styles.legendItem, styles.legendItem,
styles[style], styles[style],
colorImpairedMode && 'colorImpaired' colorImpairedMode && 'colorImpaired',
fullColorEvents && 'fullColor'
)} )}
/> />
<div className={classNames(styles.legendItemText, colorImpairedMode && styles[`${style}ColorImpaired`])}> <div className={classNames(styles.legendItemText, colorImpairedMode && styles[`${style}ColorImpaired`])}>
@@ -29,6 +31,7 @@ function LegendItem(props) {
LegendItem.propTypes = { LegendItem.propTypes = {
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
style: PropTypes.string.isRequired, style: PropTypes.string.isRequired,
fullColorEvents: PropTypes.bool.isRequired,
colorImpairedMode: PropTypes.bool.isRequired colorImpairedMode: PropTypes.bool.isRequired
}; };

View File

@@ -26,14 +26,16 @@ class CalendarOptionsModalContent extends Component {
firstDayOfWeek, firstDayOfWeek,
calendarWeekColumnHeader, calendarWeekColumnHeader,
timeFormat, timeFormat,
enableColorImpairedMode enableColorImpairedMode,
fullColorEvents
} = props; } = props;
this.state = { this.state = {
firstDayOfWeek, firstDayOfWeek,
calendarWeekColumnHeader, calendarWeekColumnHeader,
timeFormat, timeFormat,
enableColorImpairedMode enableColorImpairedMode,
fullColorEvents
}; };
} }
@@ -94,6 +96,7 @@ class CalendarOptionsModalContent extends Component {
const { const {
showMovieInformation, showMovieInformation,
showCutoffUnmetIcon, showCutoffUnmetIcon,
fullColorEvents,
onModalClose onModalClose
} = this.props; } = this.props;
@@ -136,6 +139,18 @@ class CalendarOptionsModalContent extends Component {
onChange={this.onOptionInputChange} onChange={this.onOptionInputChange}
/> />
</FormGroup> </FormGroup>
<FormGroup>
<FormLabel>{translate('FullColorEvents')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="fullColorEvents"
value={fullColorEvents}
helpText={translate('FullColorEventsHelpText')}
onChange={this.onOptionInputChange}
/>
</FormGroup>
</Form> </Form>
</FieldSet> </FieldSet>
@@ -176,7 +191,9 @@ class CalendarOptionsModalContent extends Component {
value={timeFormat} value={timeFormat}
onChange={this.onGlobalInputChange} onChange={this.onGlobalInputChange}
/> />
</FormGroup><FormGroup> </FormGroup>
<FormGroup>
<FormLabel>{translate('EnableColorImpairedMode')}</FormLabel> <FormLabel>{translate('EnableColorImpairedMode')}</FormLabel>
<FormInputGroup <FormInputGroup
@@ -187,7 +204,6 @@ class CalendarOptionsModalContent extends Component {
onChange={this.onGlobalInputChange} onChange={this.onGlobalInputChange}
/> />
</FormGroup> </FormGroup>
</Form> </Form>
</FieldSet> </FieldSet>
</ModalBody> </ModalBody>
@@ -209,6 +225,7 @@ CalendarOptionsModalContent.propTypes = {
calendarWeekColumnHeader: PropTypes.string.isRequired, calendarWeekColumnHeader: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired, timeFormat: PropTypes.string.isRequired,
enableColorImpairedMode: PropTypes.bool.isRequired, enableColorImpairedMode: PropTypes.bool.isRequired,
fullColorEvents: PropTypes.bool.isRequired,
dispatchSetCalendarOption: PropTypes.func.isRequired, dispatchSetCalendarOption: PropTypes.func.isRequired,
dispatchSaveUISettings: PropTypes.func.isRequired, dispatchSaveUISettings: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired

View File

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

View File

@@ -4,7 +4,7 @@
.year { .year {
margin-left: 5px; margin-left: 5px;
color: $disabledColor; color: var(--disabledColor);
} }
.poster { .poster {

View File

@@ -46,7 +46,7 @@ class AddNewCollectionMovieModalContent extends Component {
onInputChange, onInputChange,
rootFolderPath, rootFolderPath,
monitor, monitor,
qualityProfileId, qualityProfileIds,
minimumAvailability, minimumAvailability,
searchForMovie searchForMovie
} = this.props; } = this.props;
@@ -126,13 +126,13 @@ class AddNewCollectionMovieModalContent extends Component {
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<FormLabel>{translate('QualityProfile')}</FormLabel> <FormLabel>{translate('QualityProfiles')}</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.QUALITY_PROFILE_SELECT} type={inputTypes.QUALITY_PROFILE_SELECT}
name="qualityProfileId" name="qualityProfileIds"
onChange={this.onQualityProfileIdChange} onChange={this.onQualityProfileIdChange}
{...qualityProfileId} {...qualityProfileIds}
/> />
</FormGroup> </FormGroup>
@@ -189,7 +189,7 @@ AddNewCollectionMovieModalContent.propTypes = {
addError: PropTypes.object, addError: PropTypes.object,
rootFolderPath: PropTypes.object, rootFolderPath: PropTypes.object,
monitor: PropTypes.object.isRequired, monitor: PropTypes.object.isRequired,
qualityProfileId: PropTypes.object, qualityProfileIds: PropTypes.object,
minimumAvailability: PropTypes.object.isRequired, minimumAvailability: PropTypes.object.isRequired,
searchForMovie: PropTypes.object.isRequired, searchForMovie: PropTypes.object.isRequired,
folder: PropTypes.string.isRequired, folder: PropTypes.string.isRequired,

View File

@@ -25,7 +25,7 @@ function createMapStateToProps() {
const collectionDefaults = { const collectionDefaults = {
rootFolderPath: collection.rootFolderPath, rootFolderPath: collection.rootFolderPath,
monitor: 'movieOnly', monitor: 'movieOnly',
qualityProfileId: collection.qualityProfileId, qualityProfileIds: collection.qualityProfileIds,
minimumAvailability: collection.minimumAvailability, minimumAvailability: collection.minimumAvailability,
searchForMovie: collection.searchOnAdd, searchForMovie: collection.searchOnAdd,
tags: [] tags: []
@@ -70,7 +70,7 @@ class AddNewCollectionMovieModalContentConnector extends Component {
title, title,
rootFolderPath, rootFolderPath,
monitor, monitor,
qualityProfileId, qualityProfileIds,
minimumAvailability, minimumAvailability,
searchForMovie, searchForMovie,
tags tags
@@ -81,7 +81,7 @@ class AddNewCollectionMovieModalContentConnector extends Component {
title, title,
rootFolderPath: rootFolderPath.value, rootFolderPath: rootFolderPath.value,
monitor: monitor.value, monitor: monitor.value,
qualityProfileId: qualityProfileId.value, qualityProfileIds: qualityProfileIds.value,
minimumAvailability: minimumAvailability.value, minimumAvailability: minimumAvailability.value,
searchForMovie: searchForMovie.value, searchForMovie: searchForMovie.value,
tags: tags.value tags: tags.value
@@ -109,7 +109,7 @@ AddNewCollectionMovieModalContentConnector.propTypes = {
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
rootFolderPath: PropTypes.object, rootFolderPath: PropTypes.object,
monitor: PropTypes.object.isRequired, monitor: PropTypes.object.isRequired,
qualityProfileId: PropTypes.object, qualityProfileIds: PropTypes.object,
minimumAvailability: PropTypes.object.isRequired, minimumAvailability: PropTypes.object.isRequired,
searchForMovie: PropTypes.object.isRequired, searchForMovie: PropTypes.object.isRequired,
tags: PropTypes.object.isRequired, tags: PropTypes.object.isRequired,

View File

@@ -46,7 +46,7 @@ class EditCollectionModalContent extends Component {
const { const {
monitored, monitored,
qualityProfileId, qualityProfileIds,
minimumAvailability, minimumAvailability,
// Id, // Id,
rootFolderPath, rootFolderPath,
@@ -104,12 +104,12 @@ class EditCollectionModalContent extends Component {
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<FormLabel>{translate('QualityProfile')}</FormLabel> <FormLabel>{translate('QualityProfiles')}</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.QUALITY_PROFILE_SELECT} type={inputTypes.QUALITY_PROFILE_SELECT}
name="qualityProfileId" name="qualityProfileIds"
{...qualityProfileId} {...qualityProfileIds}
onChange={onInputChange} onChange={onInputChange}
/> />
</FormGroup> </FormGroup>

View File

@@ -39,7 +39,7 @@ function createMapStateToProps() {
const movieSettings = { const movieSettings = {
monitored: collection.monitored, monitored: collection.monitored,
qualityProfileId: collection.qualityProfileId, qualityProfileIds: collection.qualityProfileIds,
minimumAvailability: collection.minimumAvailability, minimumAvailability: collection.minimumAvailability,
rootFolderPath: collection.rootFolderPath, rootFolderPath: collection.rootFolderPath,
searchOnAdd: collection.searchOnAdd searchOnAdd: collection.searchOnAdd

View File

@@ -6,7 +6,7 @@ $hoverScale: 1.05;
&:hover { &:hover {
z-index: 2; z-index: 2;
box-shadow: 0 0 10px $black; box-shadow: 0 0 10px var(--black);
transition: all 200ms ease-in; transition: all 200ms ease-in;
.poster { .poster {
@@ -28,7 +28,7 @@ $hoverScale: 1.05;
.poster { .poster {
position: relative; position: relative;
display: block; display: block;
background-color: $defaultColor; background-color: var(--defaultColor);
} }
.overlay { .overlay {
@@ -44,7 +44,7 @@ $hoverScale: 1.05;
.overlayTitle { .overlayTitle {
padding: 5px; padding: 5px;
color: $offWhite; color: var(--offWhite);
text-align: left; text-align: left;
font-weight: bold; font-weight: bold;
font-size: 15px; font-size: 15px;
@@ -67,7 +67,7 @@ $hoverScale: 1.05;
z-index: 3; z-index: 3;
border-radius: 4px; border-radius: 4px;
background-color: #707070; background-color: #707070;
color: $white; color: var(--white);
font-size: $smallFontSize; font-size: $smallFontSize;
opacity: 0; opacity: 0;
transition: opacity 0; transition: opacity 0;
@@ -77,7 +77,7 @@ $hoverScale: 1.05;
composes: button from '~Components/Link/IconButton.css'; composes: button from '~Components/Link/IconButton.css';
&:hover { &:hover {
color: $radarrYellow; color: var(--radarrYellow);
} }
} }
@@ -102,16 +102,16 @@ $hoverScale: 1.05;
position: relative; position: relative;
display: block; display: block;
background-color: $defaultColor; background-color: var(--defaultColor);
} }
.monitorToggleButton { .monitorToggleButton {
composes: toggleButton from '~Components/MonitorToggleButton.css'; composes: toggleButton from '~Components/MonitorToggleButton.css';
width: 25px; width: 25px;
color: $white; color: var(--white);
&:hover { &:hover {
color: $iconButtonHoverLightColor; color: var(--iconButtonHoverLightColor);
} }
} }

View File

@@ -3,7 +3,7 @@
align-items: stretch; align-items: stretch;
overflow: hidden; overflow: hidden;
margin: 2px 4px; margin: 2px 4px;
border: 1px solid $borderColor; border: 1px solid var(--borderColor);
border-radius: 4px; border-radius: 4px;
background-color: #eee; background-color: #eee;
cursor: default; cursor: default;
@@ -17,34 +17,34 @@
padding: 0 4px; padding: 0 4px;
border-left: 4px; border-left: 4px;
border-left-style: solid; border-left-style: solid;
background-color: $white; background-color: var(--white);
color: $defaultColor; color: var(--defaultColor);
} }
.primary { .primary {
border-color: $primaryColor; border-color: var(--primaryColor);
} }
.danger { .danger {
border-color: $dangerColor; border-color: var(--dangerColor);
} }
.success { .success {
border-color: $successColor; border-color: var(--successColor);
} }
.purple { .purple {
border-color: $purple; border-color: var(--purple);
} }
.warning { .warning {
border-color: $warningColor; border-color: var(--warningColor);
} }
.info { .info {
border-color: $infoColor; border-color: var(--infoColor);
} }
.queue { .queue {
border-color: $queueColor; border-color: var(--queueColor);
} }

View File

@@ -17,11 +17,13 @@ class CollectionMovieLabel extends Component {
status, status,
monitored, monitored,
isAvailable, isAvailable,
hasFile,
onMonitorTogglePress, onMonitorTogglePress,
isSaving isSaving,
statistics
} = this.props; } = this.props;
const { movieFileCount } = statistics;
return ( return (
<div className={styles.movie}> <div className={styles.movie}>
<div className={styles.movieTitle}> <div className={styles.movieTitle}>
@@ -46,11 +48,11 @@ class CollectionMovieLabel extends Component {
<div <div
className={classNames( className={classNames(
styles.movieStatus, styles.movieStatus,
styles[getStatusStyle(status, monitored, hasFile, isAvailable, 'kinds')] styles[getStatusStyle(status, monitored, movieFileCount > 0, isAvailable, 'kinds')]
)} )}
> >
{ {
hasFile ? translate('Downloaded') : translate('Missing') movieFileCount > 0 ? translate('Downloaded') : translate('Missing')
} }
</div> </div>
} }
@@ -63,9 +65,9 @@ CollectionMovieLabel.propTypes = {
id: PropTypes.number, id: PropTypes.number,
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
status: PropTypes.string, status: PropTypes.string,
statistics: PropTypes.object.isRequired,
isAvailable: PropTypes.bool, isAvailable: PropTypes.bool,
monitored: PropTypes.bool, monitored: PropTypes.bool,
hasFile: PropTypes.bool,
isSaving: PropTypes.bool.isRequired, isSaving: PropTypes.bool.isRequired,
movieFile: PropTypes.object, movieFile: PropTypes.object,
movieFileId: PropTypes.number, movieFileId: PropTypes.number,
@@ -75,9 +77,7 @@ CollectionMovieLabel.propTypes = {
CollectionMovieLabel.defaultProps = { CollectionMovieLabel.defaultProps = {
isSaving: false, isSaving: false,
statistics: { statistics: {
episodeFileCount: 0, movieFileCount: 0
totalEpisodeCount: 0,
percentOfEpisodes: 0
} }
}; };

View File

@@ -104,7 +104,7 @@ $hoverScale: 1.05;
width: 25px; width: 25px;
&:hover { &:hover {
color: $iconButtonHoverLightColor; color: var(--iconButtonHoverLightColor);
} }
} }
@@ -131,7 +131,7 @@ $hoverScale: 1.05;
width: 20px; width: 20px;
&:hover { &:hover {
color: $iconButtonHoverLightColor; color: var(--iconButtonHoverLightColor);
} }
} }
} }

View File

@@ -96,7 +96,7 @@ class CollectionOverview extends Component {
render() { render() {
const { const {
monitored, monitored,
qualityProfileId, qualityProfileIds,
rootFolderPath, rootFolderPath,
genres, genres,
id, id,
@@ -212,7 +212,7 @@ class CollectionOverview extends Component {
<span className={styles.qualityProfileName}> <span className={styles.qualityProfileName}>
{ {
<QualityProfileNameConnector <QualityProfileNameConnector
qualityProfileId={qualityProfileId} qualityProfileIds={qualityProfileIds}
/> />
} }
</span> </span>
@@ -325,7 +325,7 @@ class CollectionOverview extends Component {
CollectionOverview.propTypes = { CollectionOverview.propTypes = {
id: PropTypes.number.isRequired, id: PropTypes.number.isRequired,
monitored: PropTypes.bool.isRequired, monitored: PropTypes.bool.isRequired,
qualityProfileId: PropTypes.number.isRequired, qualityProfileIds: PropTypes.number.isRequired,
minimumAvailability: PropTypes.string.isRequired, minimumAvailability: PropTypes.string.isRequired,
searchOnAdd: PropTypes.bool.isRequired, searchOnAdd: PropTypes.bool.isRequired,
rootFolderPath: PropTypes.string.isRequired, rootFolderPath: PropTypes.string.isRequired,

View File

@@ -5,7 +5,7 @@
.container { .container {
&:hover { &:hover {
.content { .content {
background-color: $tableRowHoverBackgroundColor; background-color: var(--tableRowHoverBackgroundColor);
} }
} }
} }

View File

@@ -15,6 +15,7 @@ export const REFRESH_MOVIE = 'RefreshMovie';
export const RENAME_FILES = 'RenameFiles'; export const RENAME_FILES = 'RenameFiles';
export const RENAME_MOVIE = 'RenameMovie'; export const RENAME_MOVIE = 'RenameMovie';
export const RESET_API_KEY = 'ResetApiKey'; export const RESET_API_KEY = 'ResetApiKey';
export const RESET_QUALITY_DEFINITIONS = 'ResetQualityDefinitions';
export const RSS_SYNC = 'RssSync'; export const RSS_SYNC = 'RssSync';
export const MOVIE_SEARCH = 'MoviesSearch'; export const MOVIE_SEARCH = 'MoviesSearch';
export const IMPORT_LIST_SYNC = 'ImportListSync'; export const IMPORT_LIST_SYNC = 'ImportListSync';

View File

@@ -7,25 +7,25 @@
} }
.danger { .danger {
border-color: $alertDangerBorderColor; border-color: var(--alertDangerBorderColor);
background-color: $alertDangerBackgroundColor; background-color: var(--alertDangerBackgroundColor);
color: $alertDangerColor; color: var(--alertDangerColor);
} }
.info { .info {
border-color: $alertInfoBorderColor; border-color: var(--alertInfoBorderColor);
background-color: $alertInfoBackgroundColor; background-color: var(--alertInfoBackgroundColor);
color: $alertInfoColor; color: var(--alertInfoColor);
} }
.success { .success {
border-color: $alertSuccessBorderColor; border-color: var(--alertSuccessBorderColor);
background-color: $alertSuccessBackgroundColor; background-color: var(--alertSuccessBackgroundColor);
color: $alertSuccessColor; color: var(--alertSuccessColor);
} }
.warning { .warning {
border-color: $alertWarningBorderColor; border-color: var(--alertWarningBorderColor);
background-color: $alertWarningBackgroundColor; background-color: var(--alertWarningBackgroundColor);
color: $alertWarningColor; color: var(--alertWarningColor);
} }

View File

@@ -3,9 +3,9 @@
margin: 10px; margin: 10px;
padding: 10px; padding: 10px;
border-radius: 3px; border-radius: 3px;
background-color: $white; background-color: var(--cardBackgroundColor);
box-shadow: 0 0 10px 1px $cardShadowColor; box-shadow: 0 0 10px 1px var(--cardShadowColor);
color: $defaultColor; color: var(--defaultColor);
} }
.underlay { .underlay {

View File

@@ -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 colors from 'Styles/Variables/colors';
import styles from './CircularProgressBar.css'; import styles from './CircularProgressBar.css';
class CircularProgressBar extends Component { class CircularProgressBar extends Component {
@@ -132,7 +131,7 @@ CircularProgressBar.defaultProps = {
containerClassName: styles.circularProgressBarContainer, containerClassName: styles.circularProgressBarContainer,
size: 60, size: 60,
strokeWidth: 5, strokeWidth: 5,
strokeColor: colors.radarrYellow, strokeColor: '#ffc230',
showProgressText: false showProgressText: false
}; };

View File

@@ -13,7 +13,7 @@
width: 100%; width: 100%;
border: 0; border: 0;
border-bottom: 1px solid #e5e5e5; border-bottom: 1px solid #e5e5e5;
color: #3a3f51; color: var(--textColor);
font-size: 21px; font-size: 21px;
line-height: inherit; line-height: inherit;

View File

@@ -13,7 +13,7 @@
} }
.faqLink { .faqLink {
color: $alertWarningColor; color: var(--alertWarningColor);
font-weight: bold; font-weight: bold;
} }

View File

@@ -3,7 +3,7 @@
margin-bottom: 5px; margin-bottom: 5px;
&:hover { &:hover {
background-color: $tableRowHoverBackgroundColor; background-color: var(--tableRowHoverBackgroundColor);
} }
} }

View File

@@ -17,5 +17,5 @@
.or { .or {
margin: 0 3px; margin: 0 3px;
color: $themeDarkColor; color: var(--themeDarkColor);
} }

View File

@@ -4,7 +4,7 @@
padding: 5px; padding: 5px;
&:hover { &:hover {
background-color: $tableRowHoverBackgroundColor; background-color: var(--tableRowHoverBackgroundColor);
} }
} }

View File

@@ -27,10 +27,10 @@
overflow-y: auto; overflow-y: auto;
max-height: 200px; max-height: 200px;
width: 100%; width: 100%;
border: 1px solid $inputBorderColor; border: 1px solid var(--inputBorderColor);
border-radius: 4px; border-radius: 4px;
background-color: $white; background-color: var(--inputBackgroundColor);
box-shadow: inset 0 1px 1px $inputBoxShadowColor; box-shadow: inset 0 1px 1px var(--inputBoxShadowColor);
} }
} }
@@ -46,5 +46,5 @@
} }
.suggestionHighlighted { .suggestionHighlighted {
background-color: $menuItemHoverBackgroundColor; background-color: var(--menuItemHoverBackgroundColor);
} }

View File

@@ -32,21 +32,21 @@
height: 20px; height: 20px;
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 2px; border-radius: 2px;
background-color: $white; background-color: var(--white);
color: $white; color: var(--white);
text-align: center; text-align: center;
line-height: 20px; line-height: 20px;
} }
.checkbox:focus + .input { .checkbox:focus + .input {
outline: 0; outline: 0;
border-color: $inputFocusBorderColor; border-color: var(--inputFocusBorderColor);
box-shadow: inset 0 1px 1px $inputBoxShadowColor, 0 0 8px $inputFocusBoxShadowColor; box-shadow: inset 0 1px 1px var(--inputBoxShadowColor), 0 0 8px var(--inputFocusBoxShadowColor);
} }
.dangerIsChecked { .dangerIsChecked {
border-color: $dangerColor; border-color: var(--dangerColor);
background-color: $dangerColor; background-color: var(--dangerColor);
&.isDisabled { &.isDisabled {
opacity: 0.7; opacity: 0.7;
@@ -54,8 +54,8 @@
} }
.primaryIsChecked { .primaryIsChecked {
border-color: $primaryColor; border-color: var(--primaryColor);
background-color: $primaryColor; background-color: var(--primaryColor);
&.isDisabled { &.isDisabled {
opacity: 0.7; opacity: 0.7;
@@ -63,8 +63,8 @@
} }
.successIsChecked { .successIsChecked {
border-color: $successColor; border-color: var(--successColor);
background-color: $successColor; background-color: var(--successColor);
&.isDisabled { &.isDisabled {
opacity: 0.7; opacity: 0.7;
@@ -72,8 +72,8 @@
} }
.warningIsChecked { .warningIsChecked {
border-color: $warningColor; border-color: var(--warningColor);
background-color: $warningColor; background-color: var(--warningColor);
&.isDisabled { &.isDisabled {
opacity: 0.7; opacity: 0.7;
@@ -82,15 +82,15 @@
.isNotChecked { .isNotChecked {
&.isDisabled { &.isDisabled {
border-color: $disabledCheckInputColor; border-color: var(--disabledCheckInputColor);
background-color: $disabledCheckInputColor; background-color: var(--disabledCheckInputColor);
opacity: 0.7; opacity: 0.7;
} }
} }
.isIndeterminate { .isIndeterminate {
border-color: $gray; border-color: var(--gray);
background-color: $gray; background-color: var(--gray);
} }
.helpText { .helpText {

View File

@@ -39,7 +39,7 @@
.dropdownArrowContainerDisabled { .dropdownArrowContainerDisabled {
composes: dropdownArrowContainer; composes: dropdownArrowContainer;
color: $disabledInputColor; color: var(--disabledInputColor);
} }
.optionsContainer { .optionsContainer {
@@ -50,9 +50,9 @@
.options { .options {
composes: scroller from '~Components/Scroller/Scroller.css'; composes: scroller from '~Components/Scroller/Scroller.css';
border: 1px solid $inputBorderColor; border: 1px solid var(--inputBorderColor);
border-radius: 4px; border-radius: 4px;
background-color: $white; background-color: var(--inputBackgroundColor);
} }
.optionsModal { .optionsModal {
@@ -76,9 +76,9 @@
.optionsModalScroller { .optionsModalScroller {
composes: scroller from '~Components/Scroller/Scroller.css'; composes: scroller from '~Components/Scroller/Scroller.css';
border: 1px solid $inputBorderColor; border: 1px solid var(--inputBorderColor);
border-radius: 4px; border-radius: 4px;
background-color: $white; background-color: var(--inputBackgroundColor);
} }
.loading { .loading {
@@ -90,7 +90,7 @@
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
height: 40px; height: 40px;
border-bottom: 1px solid $borderColor; border-bottom: 1px solid var(--borderColor);
} }
.mobileCloseButton { .mobileCloseButton {
@@ -100,6 +100,6 @@
line-height: 40px; line-height: 40px;
&:hover { &:hover {
color: $modalCloseButtonHoverColor; color: var(--modalCloseButtonHoverColor);
} }
} }

View File

@@ -7,7 +7,7 @@
cursor: default; cursor: default;
&:hover { &:hover {
background-color: #f8f8f8; background-color: var(--inputHoverBackgroundColor);
} }
} }
@@ -24,17 +24,17 @@
} }
.isSelected { .isSelected {
background-color: #e2e2e2; background-color: var(--inputSelectedBackgroundColor);
&:hover { &:hover {
background-color: #e2e2e2; background-color: var(--inputSelectedBackgroundColor);
} }
&.isMobile { &.isMobile {
background-color: inherit; background-color: inherit;
.iconContainer { .iconContainer {
color: $primaryColor; color: var(--primaryColor);
} }
} }
} }
@@ -49,7 +49,7 @@
.isMobile { .isMobile {
height: 50px; height: 50px;
border-bottom: 1px solid $borderColor; border-bottom: 1px solid var(--borderColor);
&:last-child { &:last-child {
border: none; border: none;

View File

@@ -3,5 +3,5 @@
} }
.isDisabled { .isDisabled {
color: $disabledInputColor; color: var(--disabledInputColor);
} }

View File

@@ -2,33 +2,19 @@ import classNames from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import Button from 'Components/Link/Button'; import Button from 'Components/Link/Button';
import SpinnerButton from 'Components/Link/SpinnerButton';
import { kinds } from 'Helpers/Props'; import { kinds } from 'Helpers/Props';
import styles from './FormInputButton.css'; import styles from './FormInputButton.css';
function FormInputButton(props) { function FormInputButton(props) {
const { const {
className, className,
canSpin, ButtonComponent,
isLastButton, isLastButton,
...otherProps ...otherProps
} = props; } = props;
if (canSpin) {
return (
<SpinnerButton
className={classNames(
className,
!isLastButton && styles.middleButton
)}
kind={kinds.PRIMARY}
{...otherProps}
/>
);
}
return ( return (
<Button <ButtonComponent
className={classNames( className={classNames(
className, className,
!isLastButton && styles.middleButton !isLastButton && styles.middleButton
@@ -41,14 +27,14 @@ function FormInputButton(props) {
FormInputButton.propTypes = { FormInputButton.propTypes = {
className: PropTypes.string.isRequired, className: PropTypes.string.isRequired,
isLastButton: PropTypes.bool.isRequired, ButtonComponent: PropTypes.elementType.isRequired,
canSpin: PropTypes.bool.isRequired isLastButton: PropTypes.bool.isRequired
}; };
FormInputButton.defaultProps = { FormInputButton.defaultProps = {
className: styles.button, className: styles.button,
isLastButton: true, ButtonComponent: Button,
canSpin: false isLastButton: true
}; };
export default FormInputButton; export default FormInputButton;

View File

@@ -6,7 +6,6 @@
.inputGroup { .inputGroup {
display: flex; display: flex;
flex: 1 1 auto; flex: 1 1 auto;
flex-wrap: wrap;
} }
.inputContainer { .inputContainer {
@@ -40,7 +39,7 @@
} }
.pendingChangesIcon { .pendingChangesIcon {
color: $warningColor; color: var(--warningColor);
font-size: 20px; font-size: 20px;
line-height: 35px; line-height: 35px;
} }

View File

@@ -20,6 +20,7 @@ import NumberInput from './NumberInput';
import OAuthInputConnector from './OAuthInputConnector'; import OAuthInputConnector from './OAuthInputConnector';
import PasswordInput from './PasswordInput'; import PasswordInput from './PasswordInput';
import PathInputConnector from './PathInputConnector'; import PathInputConnector from './PathInputConnector';
import PlexMachineInputConnector from './PlexMachineInputConnector';
import QualityProfileSelectInputConnector from './QualityProfileSelectInputConnector'; import QualityProfileSelectInputConnector from './QualityProfileSelectInputConnector';
import RootFolderSelectInputConnector from './RootFolderSelectInputConnector'; import RootFolderSelectInputConnector from './RootFolderSelectInputConnector';
import TagInputConnector from './TagInputConnector'; import TagInputConnector from './TagInputConnector';
@@ -62,6 +63,9 @@ function getComponent(type) {
case inputTypes.PATH: case inputTypes.PATH:
return PathInputConnector; return PathInputConnector;
case inputTypes.PLEX_MACHINE_SELECT:
return PlexMachineInputConnector;
case inputTypes.QUALITY_PROFILE_SELECT: case inputTypes.QUALITY_PROFILE_SELECT:
return QualityProfileSelectInputConnector; return QualityProfileSelectInputConnector;

View File

@@ -1,14 +1,14 @@
.helpText { .helpText {
margin-top: 5px; margin-top: 5px;
color: $helpTextColor; color: var(--helpTextColor);
line-height: 20px; line-height: 20px;
} }
.isError { .isError {
color: $dangerColor; color: var(--dangerColor);
.link { .link {
color: $dangerColor; color: var(--dangerColor);
&:hover { &:hover {
color: #e01313; color: #e01313;
@@ -17,10 +17,10 @@
} }
.isWarning { .isWarning {
color: $warningColor; color: var(--warningColor);
.link { .link {
color: $warningColor; color: var(--warningColor);
&:hover { &:hover {
color: #e36c00; color: #e36c00;

View File

@@ -7,11 +7,11 @@
} }
.hasError { .hasError {
color: $dangerColor; color: var(--dangerColor);
} }
.isAdvanced { .isAdvanced {
color: $advancedFormLabelColor; color: var(--advancedFormLabelColor);
} }
@media only screen and (max-width: $breakpointLarge) { @media only screen and (max-width: $breakpointLarge) {

View File

@@ -18,11 +18,11 @@
@add-mixin truncate; @add-mixin truncate;
margin-left: 15px; margin-left: 15px;
color: $darkGray; color: var(--darkGray);
font-size: $smallFontSize; font-size: $smallFontSize;
} }
.divider { .divider {
border: none; border: none;
border-bottom: 1px solid $lightGray; border-bottom: 1px solid var(--lightGray);
} }

View File

@@ -18,7 +18,7 @@
flex: 1 10 0; flex: 1 10 0;
margin-left: 15px; margin-left: 15px;
color: $gray; color: var(--gray);
text-align: right; text-align: right;
font-size: $smallFontSize; font-size: $smallFontSize;
} }

View File

@@ -2,26 +2,27 @@
padding: 6px 16px; padding: 6px 16px;
width: 100%; width: 100%;
height: 35px; height: 35px;
border: 1px solid $inputBorderColor; border: 1px solid var(--inputBorderColor);
border-radius: 4px; border-radius: 4px;
background-color: $white; background-color: var(--inputBackgroundColor);
box-shadow: inset 0 1px 1px $inputBoxShadowColor; box-shadow: inset 0 1px 1px var(--inputBoxShadowColor);
color: var(--textColor);
&:focus { &:focus {
outline: 0; outline: 0;
border-color: $inputFocusBorderColor; border-color: var(--inputFocusBorderColor);
box-shadow: inset 0 1px 1px $inputBoxShadowColor, 0 0 8px $inputFocusBoxShadowColor; box-shadow: inset 0 1px 1px var(--inputBoxShadowColor), 0 0 8px var(--inputFocusBoxShadowColor);
} }
} }
.hasError { .hasError {
border-color: $inputErrorBorderColor; border-color: var(--inputErrorBorderColor);
box-shadow: inset 0 1px 1px $inputBoxShadowColor, 0 0 8px $inputErrorBoxShadowColor; box-shadow: inset 0 1px 1px var(--inputBoxShadowColor), 0 0 8px var(--inputErrorBoxShadowColor);
} }
.hasWarning { .hasWarning {
border-color: $inputWarningBorderColor; border-color: var(--inputWarningBorderColor);
box-shadow: inset 0 1px 1px $inputBoxShadowColor, 0 0 8px $inputWarningBoxShadowColor; box-shadow: inset 0 1px 1px var(--inputBoxShadowColor), 0 0 8px var(--inputWarningBoxShadowColor);
} }
.hasButton { .hasButton {

View File

@@ -7,8 +7,8 @@
&.isFocused { &.isFocused {
outline: 0; outline: 0;
border-color: $inputFocusBorderColor; border-color: var(--inputFocusBorderColor);
box-shadow: inset 0 1px 1px $inputBoxShadowColor, 0 0 8px $inputFocusBoxShadowColor; box-shadow: inset 0 1px 1px var(--inputBoxShadowColor), 0 0 8px var(--inputFocusBoxShadowColor);
} }
} }

View File

@@ -1,7 +1,7 @@
.itemContainer { .itemContainer {
display: flex; display: flex;
margin-bottom: 3px; margin-bottom: 3px;
border-bottom: 1px solid $inputBorderColor; border-bottom: 1px solid var(--inputBorderColor);
&:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;

View File

@@ -5,6 +5,7 @@ import { kinds } from 'Helpers/Props';
function OAuthInput(props) { function OAuthInput(props) {
const { const {
className,
label, label,
authorizing, authorizing,
error, error,
@@ -12,21 +13,21 @@ function OAuthInput(props) {
} = props; } = props;
return ( return (
<div> <SpinnerErrorButton
<SpinnerErrorButton className={className}
kind={kinds.PRIMARY} kind={kinds.PRIMARY}
isSpinning={authorizing} isSpinning={authorizing}
error={error} error={error}
onPress={onPress} onPress={onPress}
> >
{label} {label}
</SpinnerErrorButton> </SpinnerErrorButton>
</div>
); );
} }
OAuthInput.propTypes = { OAuthInput.propTypes = {
label: PropTypes.string.isRequired, className: PropTypes.string,
label: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
authorizing: PropTypes.bool.isRequired, authorizing: PropTypes.bool.isRequired,
error: PropTypes.object, error: PropTypes.object,
onPress: PropTypes.func.isRequired onPress: PropTypes.func.isRequired

View File

@@ -0,0 +1,44 @@
import PropTypes from 'prop-types';
import React from 'react';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import SelectInput from './SelectInput';
function PlexMachineInput(props) {
const {
isFetching,
isDisabled,
value,
values,
onChange,
...otherProps
} = props;
const helpText = 'Authenticate with plex.tv to show servers to use for authentication';
return (
<>
{
isFetching ?
<LoadingIndicator /> :
<SelectInput
value={value}
values={values}
isDisabled={isDisabled}
onChange={onChange}
helpText={helpText}
{...otherProps}
/>
}
</>
);
}
PlexMachineInput.propTypes = {
isFetching: PropTypes.bool.isRequired,
isDisabled: PropTypes.bool.isRequired,
value: PropTypes.string,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
onChange: PropTypes.func.isRequired
};
export default PlexMachineInput;

View File

@@ -0,0 +1,115 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchPlexResources } from 'Store/Actions/settingsActions';
import PlexMachineInput from './PlexMachineInput';
function createMapStateToProps() {
return createSelector(
(state, { value }) => value,
(state) => state.oAuth,
(state) => state.settings.plex,
(value, oAuth, plex) => {
let values = [{ key: value, value }];
let isDisabled = true;
if (plex.isPopulated) {
const serverValues = plex.items.filter((item) => item.provides.includes('server')).map((item) => {
return ({
key: item.clientIdentifier,
value: `${item.name} / ${item.owned ? 'Owner' : 'User'} / ${item.clientIdentifier}`
});
});
if (serverValues.find((item) => item.key === value)) {
values = serverValues;
} else {
values = values.concat(serverValues);
}
isDisabled = false;
}
return ({
accessToken: oAuth.result?.accessToken,
values,
isDisabled,
...plex
});
}
);
}
const mapDispatchToProps = {
dispatchFetchPlexResources: fetchPlexResources
};
class PlexMachineInputConnector extends Component {
//
// Lifecycle
componentDidMount = () => {
const {
accessToken,
dispatchFetchPlexResources
} = this.props;
if (accessToken) {
dispatchFetchPlexResources({ accessToken });
}
};
componentDidUpdate(prevProps) {
const {
accessToken,
dispatchFetchPlexResources
} = this.props;
const oldToken = prevProps.accessToken;
if (accessToken && accessToken !== oldToken) {
dispatchFetchPlexResources({ accessToken });
}
}
render() {
const {
isFetching,
isPopulated,
isDisabled,
value,
values,
onChange
} = this.props;
return (
<PlexMachineInput
isFetching={isFetching}
isPopulated={isPopulated}
isDisabled={isDisabled}
value={value}
values={values}
onChange={onChange}
{...this.props}
/>
);
}
}
PlexMachineInputConnector.propTypes = {
dispatchFetchPlexResources: PropTypes.func.isRequired,
name: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
isDisabled: PropTypes.bool.isRequired,
error: PropTypes.object,
oAuth: PropTypes.object,
accessToken: PropTypes.string,
onChange: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(PlexMachineInputConnector);

View File

@@ -5,7 +5,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector'; import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import sortByName from 'Utilities/Array/sortByName'; import sortByName from 'Utilities/Array/sortByName';
import SelectInput from './SelectInput'; import EnhancedSelectInput from './EnhancedSelectInput';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
@@ -45,40 +45,14 @@ function createMapStateToProps() {
class QualityProfileSelectInputConnector extends Component { class QualityProfileSelectInputConnector extends Component {
//
// Lifecycle
componentDidMount() {
const {
name,
value,
values
} = this.props;
if (!value || !values.some((v) => v.key === value) ) {
const firstValue = _.find(values, (option) => !isNaN(parseInt(option.key)));
if (firstValue) {
this.onChange({ name, value: firstValue.key });
}
}
}
//
// Listeners
onChange = ({ name, value }) => {
this.props.onChange({ name, value: parseInt(value) });
};
// //
// Render // Render
render() { render() {
return ( return (
<SelectInput <EnhancedSelectInput
{...this.props} {...this.props}
onChange={this.onChange} onChange={this.props.onChange}
/> />
); );
} }
@@ -86,7 +60,7 @@ class QualityProfileSelectInputConnector extends Component {
QualityProfileSelectInputConnector.propTypes = { QualityProfileSelectInputConnector.propTypes = {
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), value: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.number), PropTypes.arrayOf(PropTypes.string)]),
values: PropTypes.arrayOf(PropTypes.object).isRequired, values: PropTypes.arrayOf(PropTypes.object).isRequired,
includeNoChange: PropTypes.bool.isRequired, includeNoChange: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired onChange: PropTypes.func.isRequired

View File

@@ -21,17 +21,17 @@
.movieFolder { .movieFolder {
@add-mixin truncate; @add-mixin truncate;
color: $disabledColor; color: var(--disabledColor);
} }
.freeSpace { .freeSpace {
margin-left: 15px; margin-left: 15px;
color: $darkGray; color: var(--darkGray);
font-size: $smallFontSize; font-size: $smallFontSize;
} }
.isMissing { .isMissing {
margin-left: 15px; margin-left: 15px;
color: $dangerColor; color: var(--dangerColor);
font-size: $smallFontSize; font-size: $smallFontSize;
} }

View File

@@ -20,13 +20,13 @@
.movieFolder { .movieFolder {
@add-mixin truncate; @add-mixin truncate;
flex: 0 1 auto; flex: 0 1 auto;
color: $disabledColor; color: var(--disabledColor);
} }
.freeSpace { .freeSpace {
flex: 0 0 auto; flex: 0 0 auto;
margin-left: 15px; margin-left: 15px;
color: $gray; color: var(--gray);
text-align: right; text-align: right;
font-size: $smallFontSize; font-size: $smallFontSize;
} }

View File

@@ -12,6 +12,10 @@
composes: hasWarning from '~Components/Form/Input.css'; composes: hasWarning from '~Components/Form/Input.css';
} }
.hasButton {
composes: hasButton from '~Components/Form/Input.css';
}
.isDisabled { .isDisabled {
opacity: 0.7; opacity: 0.7;
cursor: not-allowed; cursor: not-allowed;

View File

@@ -28,6 +28,7 @@ class SelectInput extends Component {
isDisabled, isDisabled,
hasError, hasError,
hasWarning, hasWarning,
hasButton,
autoFocus, autoFocus,
onBlur onBlur
} = this.props; } = this.props;
@@ -38,6 +39,7 @@ class SelectInput extends Component {
className, className,
hasError && styles.hasError, hasError && styles.hasError,
hasWarning && styles.hasWarning, hasWarning && styles.hasWarning,
hasButton && styles.hasButton,
isDisabled && disabledClassName isDisabled && disabledClassName
)} )}
disabled={isDisabled} disabled={isDisabled}
@@ -80,6 +82,7 @@ SelectInput.propTypes = {
isDisabled: PropTypes.bool, isDisabled: PropTypes.bool,
hasError: PropTypes.bool, hasError: PropTypes.bool,
hasWarning: PropTypes.bool, hasWarning: PropTypes.bool,
hasButton: PropTypes.bool,
autoFocus: PropTypes.bool.isRequired, autoFocus: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
onBlur: PropTypes.func onBlur: PropTypes.func

View File

@@ -7,8 +7,8 @@
&.isFocused { &.isFocused {
outline: 0; outline: 0;
border-color: $inputFocusBorderColor; border-color: var(--inputFocusBorderColor);
box-shadow: inset 0 1px 1px $inputBoxShadowColor, 0 0 8px $inputFocusBoxShadowColor; box-shadow: inset 0 1px 1px var(--inputBoxShadowColor), 0 0 8px var(--inputFocusBoxShadowColor);
} }
} }
@@ -20,4 +20,6 @@
width: 0%; width: 0%;
height: 31px; height: 31px;
border: none; border: none;
background-color: var(--inputBackground);
color: var(--textColor);
} }

View File

@@ -3,7 +3,7 @@
} }
.readOnly { .readOnly {
background-color: #eee; background-color: var(--inputReadOnlyBackgroundColor);
} }
.hasError { .hasError {

View File

@@ -49,5 +49,5 @@
} }
.readOnly { .readOnly {
background-color: #eee; background-color: var(--inputReadOnlyBackgroundColor);
} }

View File

@@ -1,5 +1,5 @@
.danger { .danger {
color: $dangerColor; color: var(--dangerColor);
} }
.default { .default {
@@ -7,25 +7,33 @@
} }
.disabled { .disabled {
color: $disabledColor; color: var(--disabledColor);
} }
.info { .info {
color: $infoColor; color: var(--infoColor);
&:global(.darken) {
color: color(var(--infoColor) shade(30%));
}
} }
.pink { .pink {
color: $pink; color: var(--pink);
&:global(.darken) {
color: color(var(--pink) shade(30%));
}
} }
.success { .success {
color: $successColor; color: var(--successColor);
} }
.warning { .warning {
color: $warningColor; color: var(--warningColor);
} }
.purple { .purple {
color: $purple; color: var(--purple);
} }

View File

@@ -18,6 +18,7 @@ class Icon extends PureComponent {
kind, kind,
size, size,
title, title,
darken,
isSpinning, isSpinning,
...otherProps ...otherProps
} = this.props; } = this.props;
@@ -26,7 +27,8 @@ class Icon extends PureComponent {
<FontAwesomeIcon <FontAwesomeIcon
className={classNames( className={classNames(
className, className,
styles[kind] styles[kind],
darken && 'darken'
)} )}
icon={name} icon={name}
spin={isSpinning} spin={isSpinning}
@@ -59,6 +61,7 @@ Icon.propTypes = {
kind: PropTypes.string.isRequired, kind: PropTypes.string.isRequired,
size: PropTypes.number.isRequired, size: PropTypes.number.isRequired,
title: PropTypes.string, title: PropTypes.string,
darken: PropTypes.bool.isRequired,
isSpinning: PropTypes.bool.isRequired, isSpinning: PropTypes.bool.isRequired,
fixedWidth: PropTypes.bool.isRequired fixedWidth: PropTypes.bool.isRequired
}; };
@@ -66,6 +69,7 @@ Icon.propTypes = {
Icon.defaultProps = { Icon.defaultProps = {
kind: kinds.DEFAULT, kind: kinds.DEFAULT,
size: 14, size: 14,
darken: false,
isSpinning: false, isSpinning: false,
fixedWidth: false fixedWidth: false
}; };

View File

@@ -1,7 +1,7 @@
.label { .label {
display: inline-block; display: inline-block;
margin: 2px; margin: 2px;
color: $white; color: var(--white);
/** text-align: center; **/ /** text-align: center; **/
white-space: nowrap; white-space: nowrap;
line-height: 1; line-height: 1;
@@ -10,7 +10,7 @@
.title { .title {
margin-bottom: 2px; margin-bottom: 2px;
color: $helpTextColor; color: var(--helpTextColor);
font-size: 10px; font-size: 10px;
} }
@@ -36,5 +36,5 @@
/** Outline **/ /** Outline **/
.outline { .outline {
background-color: $white; background-color: var(--white);
} }

View File

@@ -3,7 +3,7 @@
margin: 2px; margin: 2px;
border: 1px solid; border: 1px solid;
border-radius: 2px; border-radius: 2px;
color: $white; color: var(--white);
text-align: center; text-align: center;
white-space: nowrap; white-space: nowrap;
line-height: 1; line-height: 1;
@@ -13,94 +13,95 @@
/** Kinds **/ /** Kinds **/
.danger { .danger {
border-color: $dangerColor; border-color: var(--dangerColor);
background-color: $dangerColor; background-color: var(--dangerColor);
&.outline { &.outline {
color: $dangerColor; color: var(--dangerColor);
} }
&:global(.colorImpaired) { &:global(.colorImpaired) {
background: repeating-linear-gradient(90deg, color($dangerColor shade(5%)), color($dangerColor shade(5%)) 5px, color($dangerColor shade(15%)) 5px, color($dangerColor shade(15%)) 10px); background: repeating-linear-gradient(90deg, color(var(--dangerColor) shade(5%)), color(var(--dangerColor) shade(5%)) 5px, color(var(--dangerColor) shade(15%)) 5px, color(var(--dangerColor) shade(15%)) 10px);
} }
} }
.default { .default {
border-color: $themeLightColor; border-color: var(--themeLightColor);
background-color: $themeLightColor; background-color: var(--themeLightColor);
&.outline { &.outline {
color: $themeLightColor; color: var(--themeLightColor);
} }
} }
.disabled { .disabled {
border-color: $disabledColor; border-color: var(--disabledColor);
background-color: $disabledColor; background-color: var(--disabledColor);
&.outline { &.outline {
color: $disabledColor; color: var(--offWhite);
} }
} }
.info { .info {
border-color: $infoColor; border-color: var(--infoColor);
background-color: $infoColor; background-color: var(--infoColor);
color: var(--infoTextColor);
&.outline { &.outline {
color: $infoColor; color: var(--infoColor);
} }
} }
.inverse { .inverse {
border-color: $lightGray; border-color: var(--inverseLabelColor);
background-color: $lightGray; background-color: var(--inverseLabelColor);
color: $defaultColor; color: var(--inverseLabelTextColor);
&.outline { &.outline {
background-color: $defaultColor !important; background-color: var(--inverseLabelTextColor) !important;
color: $lightGray; color: var(--inverseLabelColor);
} }
} }
.primary { .primary {
border-color: $primaryColor; border-color: var(--primaryColor);
background-color: $primaryColor; background-color: var(--primaryColor);
&.outline { &.outline {
color: $primaryColor; color: var(--primaryColor);
} }
} }
.success { .success {
border-color: $successColor; border-color: var(--successColor);
background-color: $successColor; background-color: var(--successColor);
color: #eee; color: #eee;
&.outline { &.outline {
color: $successColor; color: var(--successColor);
} }
} }
.warning { .warning {
border-color: $warningColor; border-color: var(--warningColor);
background-color: $warningColor; background-color: var(--warningColor);
&.outline { &.outline {
color: $warningColor; color: var(--warningColor);
} }
&:global(.colorImpaired) { &:global(.colorImpaired) {
background: repeating-linear-gradient(45deg, $warningColor, $warningColor 5px, color($warningColor tint(15%)) 5px, color($warningColor tint(15%)) 10px); background: repeating-linear-gradient(45deg, var(--warningColor), var(--warningColor) 5px, color(var(--warningColor) tint(15%)) 5px, color(var(--warningColor) tint(15%)) 10px);
} }
} }
.queue { .queue {
border-color: $queueColor; border-color: var(--queueColor);
background-color: $queueColor; background-color: var(--queueColor);
&.outline { &.outline {
color: $queueColor; color: var(--queueColor);
} }
} }
@@ -125,5 +126,5 @@
/** Outline **/ /** Outline **/
.outline { .outline {
background-color: $white; background-color: var(--disabledLabelColor);
} }

View File

@@ -19,62 +19,62 @@
} }
.danger { .danger {
border-color: $dangerBorderColor; border-color: var(--dangerBorderColor);
background-color: $dangerBackgroundColor; background-color: var(--dangerBackgroundColor);
color: $white; color: var(--white);
&:hover { &:hover {
border-color: $dangerHoverBorderColor; border-color: var(--dangerHoverBorderColor);
background-color: $dangerHoverBackgroundColor; background-color: var(--dangerHoverBackgroundColor);
color: $white; color: var(--white);
} }
} }
.default { .default {
border-color: $defaultBorderColor; border-color: var(--defaultBorderColor);
background-color: $defaultBackgroundColor; background-color: var(--defaultBackgroundColor);
color: $defaultColor; color: var(--defaultColor);
&:hover { &:hover {
border-color: $defaultHoverBorderColor; border-color: var(--defaultHoverBorderColor);
background-color: $defaultHoverBackgroundColor; background-color: var(--defaultHoverBackgroundColor);
color: $defaultColor; color: var(--defaultColor);
} }
} }
.primary { .primary {
border-color: $primaryBorderColor; border-color: var(--primaryBorderColor);
background-color: $primaryBackgroundColor; background-color: var(--primaryBackgroundColor);
color: $white; color: var(--white);
&:hover { &:hover {
border-color: $primaryHoverBorderColor; border-color: var(--primaryHoverBorderColor);
background-color: $primaryHoverBackgroundColor; background-color: var(--primaryHoverBackgroundColor);
color: $white; color: var(--white);
} }
} }
.success { .success {
border-color: $successBorderColor; border-color: var(--successBorderColor);
background-color: $successBackgroundColor; background-color: var(--successBackgroundColor);
color: $white; color: var(--white);
&:hover { &:hover {
border-color: $successHoverBorderColor; border-color: var(--successHoverBorderColor);
background-color: $successHoverBackgroundColor; background-color: var(--successHoverBackgroundColor);
color: $white; color: var(--white);
} }
} }
.warning { .warning {
border-color: $warningBorderColor; border-color: var(--warningBorderColor);
background-color: $warningBackgroundColor; background-color: var(--warningBackgroundColor);
color: $white; color: var(--white);
&:hover { &:hover {
border-color: $warningHoverBorderColor; border-color: var(--warningHoverBorderColor);
background-color: $warningHoverBackgroundColor; background-color: var(--warningHoverBackgroundColor);
color: $white; color: var(--white);
} }
} }

View File

@@ -12,10 +12,10 @@
&:hover { &:hover {
border: none; border: none;
background-color: inherit; background-color: inherit;
color: $iconButtonHoverColor; color: var(--iconButtonHoverColor);
} }
&.isDisabled { &.isDisabled {
color: $iconButtonDisabledColor; color: var(--iconButtonDisabledColor);
} }
} }

View File

@@ -15,10 +15,10 @@
} }
.to { .to {
color: $linkColor; color: var(--linkColor);
&:hover { &:hover {
color: $linkHoverColor; color: var(--linkHoverColor);
text-decoration: underline; text-decoration: underline;
} }
} }

View File

@@ -26,7 +26,7 @@
.ripple { .ripple {
position: absolute; position: absolute;
border: 2px solid #3a3f51; border: 2px solid var(--themeDarkColor);
border-radius: 100%; border-radius: 100%;
animation: rippleContainer 1.25s 0s infinite cubic-bezier(0.21, 0.53, 0.56, 0.8); animation: rippleContainer 1.25s 0s infinite cubic-bezier(0.21, 0.53, 0.56, 0.8);
animation-fill-mode: both; animation-fill-mode: both;

View File

@@ -10,12 +10,12 @@
} }
&:hover { &:hover {
color: $toobarButtonHoverColor; color: var(--toobarButtonHoverColor);
} }
} }
.isDisabled { .isDisabled {
color: $disabledColor; color: var(--disabledColor);
pointer-events: none; pointer-events: none;
} }

View File

@@ -2,7 +2,7 @@
z-index: $popperZIndex; z-index: $popperZIndex;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background-color: $toolbarMenuItemBackgroundColor; background-color: var(--toolbarMenuItemBackgroundColor);
line-height: 20px; line-height: 20px;
} }

View File

@@ -5,19 +5,19 @@
padding: 10px 20px; padding: 10px 20px;
min-width: 150px; min-width: 150px;
max-width: 250px; max-width: 250px;
background-color: $toolbarMenuItemBackgroundColor; background-color: var(--toolbarMenuItemBackgroundColor);
color: $menuItemColor; color: var(--menuItemColor);
line-height: 20px; line-height: 20px;
&:hover, &:hover,
&:focus { &:focus {
background-color: $toolbarMenuItemHoverBackgroundColor; background-color: var(--toolbarMenuItemHoverBackgroundColor);
color: $menuItemHoverColor; color: var(--menuItemHoverColor);
text-decoration: none; text-decoration: none;
} }
} }
.isDisabled { .isDisabled {
color: $disabledColor; color: var(--disabledColor);
pointer-events: none; pointer-events: none;
} }

View File

@@ -2,5 +2,5 @@
overflow: hidden; overflow: hidden;
min-height: 1px; min-height: 1px;
height: 1px; height: 1px;
background-color: $themeDarkColor; background-color: var(--themeDarkColor);
} }

View File

@@ -33,7 +33,7 @@ function ConfirmModal(props) {
return () => unbindShortcut('enter', onConfirm); return () => unbindShortcut('enter', onConfirm);
} }
}, [isOpen, onConfirm]); }, [bindShortcut, unbindShortcut, isOpen, onConfirm]);
return ( return (
<Modal <Modal

View File

@@ -12,7 +12,7 @@
justify-content: center; justify-content: center;
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: $modalBackdropBackgroundColor; background-color: var(--modalBackdropBackgroundColor);
opacity: 1; opacity: 1;
} }

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