mirror of
https://github.com/Radarr/Radarr.git
synced 2026-04-18 21:35:51 -04:00
Compare commits
63 Commits
v4.4.1.6926
...
zeus-old
| Author | SHA1 | Date | |
|---|---|---|---|
| 66aeb19d88 | |||
| 455aca85bd | |||
| a29d794cbe | |||
| bb3123772f | |||
| 46a20e1dcd | |||
| 993144b67a | |||
| 1f209848dc | |||
| 3dafe44fed | |||
| 767e75ca45 | |||
| 1d4db26f17 | |||
| 757cb9a956 | |||
| 5dc3726023 | |||
| 5b2f30227b | |||
| ed94eee859 | |||
| 6eb271eee4 | |||
| 9fe205727c | |||
| 4627093616 | |||
| a006984d5e | |||
| 7d9183ef12 | |||
| d35c6683e9 | |||
| ac26bcddd9 | |||
| 15bafce8cc | |||
| 2167da87ce | |||
| 926d37a572 | |||
| 42c9e4e3e5 | |||
| 89b609a221 | |||
| dfc9f74116 | |||
| 189603c756 | |||
| a78693a2f7 | |||
| cea0c5033a | |||
| 1e3a42bf42 | |||
| 030744ab7b | |||
| 17fda02d8c | |||
| aabf6b9ff8 | |||
| 7b2fd5140b | |||
| b6b10d7c6f | |||
| 16fcf0b56b | |||
| c222a1a434 | |||
| c6e91e028b | |||
| fcf5984944 | |||
| fdfe8ca656 | |||
| 150a5c1fc6 | |||
| 9ea0957351 | |||
| 8befa436cc | |||
| 53fdb6f07f | |||
| 0697dbff96 | |||
| 34924859aa | |||
| 9c86598b54 | |||
| 0fe2262162 | |||
| 47353aea75 | |||
| af43cb2aca | |||
| bc838b74c7 | |||
| cbcf3d1058 | |||
| c72e64f081 | |||
| e09607edb0 | |||
| d91578aee3 | |||
| affedd7f9d | |||
| c3665e9fea | |||
| 364d8bd7c5 | |||
| 7142d1f224 | |||
| 86777e021b | |||
| 9d2dacea97 | |||
| d98c86c3d9 |
+15
-11
@@ -19,10 +19,10 @@ indent_size = 4
|
|||||||
dotnet_sort_system_directives_first = true
|
dotnet_sort_system_directives_first = true
|
||||||
|
|
||||||
# Avoid "this." and "Me." if not necessary
|
# Avoid "this." and "Me." if not necessary
|
||||||
dotnet_style_qualification_for_field = false:warning
|
dotnet_style_qualification_for_field = false:refactoring
|
||||||
dotnet_style_qualification_for_property = false:warning
|
dotnet_style_qualification_for_property = false:refactoring
|
||||||
dotnet_style_qualification_for_method = false:warning
|
dotnet_style_qualification_for_method = false:refactoring
|
||||||
dotnet_style_qualification_for_event = false:warning
|
dotnet_style_qualification_for_event = false:refactoring
|
||||||
|
|
||||||
# Indentation preferences
|
# Indentation preferences
|
||||||
csharp_indent_block_contents = true
|
csharp_indent_block_contents = true
|
||||||
@@ -32,6 +32,10 @@ csharp_indent_case_contents_when_block = true
|
|||||||
csharp_indent_switch_labels = true
|
csharp_indent_switch_labels = true
|
||||||
csharp_indent_labels = flush_left
|
csharp_indent_labels = flush_left
|
||||||
|
|
||||||
|
dotnet_style_qualification_for_field = false:suggestion
|
||||||
|
dotnet_style_qualification_for_property = false:suggestion
|
||||||
|
dotnet_style_qualification_for_method = false:suggestion
|
||||||
|
dotnet_style_qualification_for_event = false:suggestion
|
||||||
dotnet_naming_style.instance_field_style.capitalization = camel_case
|
dotnet_naming_style.instance_field_style.capitalization = camel_case
|
||||||
dotnet_naming_style.instance_field_style.required_prefix = _
|
dotnet_naming_style.instance_field_style.required_prefix = _
|
||||||
|
|
||||||
@@ -64,6 +68,7 @@ dotnet_diagnostic.SA1406.severity = suggestion
|
|||||||
dotnet_diagnostic.SA1410.severity = suggestion
|
dotnet_diagnostic.SA1410.severity = suggestion
|
||||||
dotnet_diagnostic.SA1411.severity = suggestion
|
dotnet_diagnostic.SA1411.severity = suggestion
|
||||||
dotnet_diagnostic.SA1413.severity = none
|
dotnet_diagnostic.SA1413.severity = none
|
||||||
|
dotnet_diagnostic.SA1512.severity = none
|
||||||
dotnet_diagnostic.SA1516.severity = none
|
dotnet_diagnostic.SA1516.severity = none
|
||||||
dotnet_diagnostic.SA1600.severity = none
|
dotnet_diagnostic.SA1600.severity = none
|
||||||
dotnet_diagnostic.SA1601.severity = none
|
dotnet_diagnostic.SA1601.severity = none
|
||||||
@@ -162,6 +167,7 @@ dotnet_diagnostic.CA1309.severity = suggestion
|
|||||||
dotnet_diagnostic.CA1310.severity = suggestion
|
dotnet_diagnostic.CA1310.severity = suggestion
|
||||||
dotnet_diagnostic.CA1401.severity = suggestion
|
dotnet_diagnostic.CA1401.severity = suggestion
|
||||||
dotnet_diagnostic.CA1416.severity = suggestion
|
dotnet_diagnostic.CA1416.severity = suggestion
|
||||||
|
dotnet_diagnostic.CA1419.severity = suggestion
|
||||||
dotnet_diagnostic.CA1507.severity = suggestion
|
dotnet_diagnostic.CA1507.severity = suggestion
|
||||||
dotnet_diagnostic.CA1508.severity = suggestion
|
dotnet_diagnostic.CA1508.severity = suggestion
|
||||||
dotnet_diagnostic.CA1707.severity = suggestion
|
dotnet_diagnostic.CA1707.severity = suggestion
|
||||||
@@ -177,9 +183,6 @@ dotnet_diagnostic.CA1720.severity = suggestion
|
|||||||
dotnet_diagnostic.CA1721.severity = suggestion
|
dotnet_diagnostic.CA1721.severity = suggestion
|
||||||
dotnet_diagnostic.CA1724.severity = suggestion
|
dotnet_diagnostic.CA1724.severity = suggestion
|
||||||
dotnet_diagnostic.CA1725.severity = suggestion
|
dotnet_diagnostic.CA1725.severity = suggestion
|
||||||
dotnet_diagnostic.CA1801.severity = suggestion
|
|
||||||
dotnet_diagnostic.CA1802.severity = suggestion
|
|
||||||
dotnet_diagnostic.CA1805.severity = suggestion
|
|
||||||
dotnet_diagnostic.CA1806.severity = suggestion
|
dotnet_diagnostic.CA1806.severity = suggestion
|
||||||
dotnet_diagnostic.CA1810.severity = suggestion
|
dotnet_diagnostic.CA1810.severity = suggestion
|
||||||
dotnet_diagnostic.CA1812.severity = suggestion
|
dotnet_diagnostic.CA1812.severity = suggestion
|
||||||
@@ -191,13 +194,11 @@ dotnet_diagnostic.CA1819.severity = suggestion
|
|||||||
dotnet_diagnostic.CA1822.severity = suggestion
|
dotnet_diagnostic.CA1822.severity = suggestion
|
||||||
dotnet_diagnostic.CA1823.severity = suggestion
|
dotnet_diagnostic.CA1823.severity = suggestion
|
||||||
dotnet_diagnostic.CA1824.severity = suggestion
|
dotnet_diagnostic.CA1824.severity = suggestion
|
||||||
|
dotnet_diagnostic.CA1848.severity = suggestion
|
||||||
dotnet_diagnostic.CA2000.severity = suggestion
|
dotnet_diagnostic.CA2000.severity = suggestion
|
||||||
dotnet_diagnostic.CA2002.severity = suggestion
|
dotnet_diagnostic.CA2002.severity = suggestion
|
||||||
dotnet_diagnostic.CA2007.severity = suggestion
|
dotnet_diagnostic.CA2007.severity = suggestion
|
||||||
dotnet_diagnostic.CA2008.severity = suggestion
|
dotnet_diagnostic.CA2008.severity = suggestion
|
||||||
dotnet_diagnostic.CA2009.severity = suggestion
|
|
||||||
dotnet_diagnostic.CA2010.severity = suggestion
|
|
||||||
dotnet_diagnostic.CA2011.severity = suggestion
|
|
||||||
dotnet_diagnostic.CA2012.severity = suggestion
|
dotnet_diagnostic.CA2012.severity = suggestion
|
||||||
dotnet_diagnostic.CA2013.severity = suggestion
|
dotnet_diagnostic.CA2013.severity = suggestion
|
||||||
dotnet_diagnostic.CA2100.severity = suggestion
|
dotnet_diagnostic.CA2100.severity = suggestion
|
||||||
@@ -228,6 +229,9 @@ dotnet_diagnostic.CA2243.severity = suggestion
|
|||||||
dotnet_diagnostic.CA2244.severity = suggestion
|
dotnet_diagnostic.CA2244.severity = suggestion
|
||||||
dotnet_diagnostic.CA2245.severity = suggestion
|
dotnet_diagnostic.CA2245.severity = suggestion
|
||||||
dotnet_diagnostic.CA2246.severity = suggestion
|
dotnet_diagnostic.CA2246.severity = suggestion
|
||||||
|
dotnet_diagnostic.CA2249.severity = suggestion
|
||||||
|
dotnet_diagnostic.CA2251.severity = suggestion
|
||||||
|
dotnet_diagnostic.CA2254.severity = suggestion
|
||||||
dotnet_diagnostic.CA3061.severity = suggestion
|
dotnet_diagnostic.CA3061.severity = suggestion
|
||||||
dotnet_diagnostic.CA3075.severity = suggestion
|
dotnet_diagnostic.CA3075.severity = suggestion
|
||||||
dotnet_diagnostic.CA3076.severity = suggestion
|
dotnet_diagnostic.CA3076.severity = suggestion
|
||||||
@@ -255,7 +259,7 @@ dotnet_diagnostic.CA5392.severity = suggestion
|
|||||||
dotnet_diagnostic.CA5394.severity = suggestion
|
dotnet_diagnostic.CA5394.severity = suggestion
|
||||||
dotnet_diagnostic.CA5397.severity = suggestion
|
dotnet_diagnostic.CA5397.severity = suggestion
|
||||||
|
|
||||||
|
dotnet_diagnostic.SYSLIB0006.severity = none
|
||||||
|
|
||||||
[*.{js,html,js,hbs,less,css}]
|
[*.{js,html,js,hbs,less,css}]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"paths": [
|
|
||||||
"frontend/src/**/*.js"
|
|
||||||
],
|
|
||||||
"ignored": [
|
|
||||||
"**/node_modules/**/*"
|
|
||||||
],
|
|
||||||
"port": 5004
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
name: Sync issue to Azure DevOps work item
|
|
||||||
|
|
||||||
on:
|
|
||||||
issues:
|
|
||||||
types:
|
|
||||||
[opened, edited, deleted, closed, reopened, labeled, unlabeled, assigned]
|
|
||||||
|
|
||||||
concurrency: azuresync-${{ github.event.issue.number }}
|
|
||||||
|
|
||||||
permissions: {}
|
|
||||||
jobs:
|
|
||||||
alert:
|
|
||||||
permissions:
|
|
||||||
issues: write # to update issue body
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: danhellem/github-actions-issue-to-work-item@master
|
|
||||||
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == true }}"
|
|
||||||
env:
|
|
||||||
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
|
|
||||||
github_token: "${{ github.token }}"
|
|
||||||
ado_organization: "Servarr"
|
|
||||||
ado_project: "Servarr"
|
|
||||||
ado_area_path: "Servarr\\Radarr"
|
|
||||||
ado_wit: "Bug"
|
|
||||||
ado_new_state: "New"
|
|
||||||
ado_active_state: "Active"
|
|
||||||
ado_close_state: "Closed"
|
|
||||||
ado_bypassrules: true
|
|
||||||
log_level: 100
|
|
||||||
- uses: danhellem/github-actions-issue-to-work-item@master
|
|
||||||
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == false }}"
|
|
||||||
env:
|
|
||||||
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
|
|
||||||
github_token: "${{ github.token }}"
|
|
||||||
ado_organization: "Servarr"
|
|
||||||
ado_project: "Servarr"
|
|
||||||
ado_area_path: "Servarr\\Radarr"
|
|
||||||
ado_wit: "User Story"
|
|
||||||
ado_new_state: "New"
|
|
||||||
ado_active_state: "Active"
|
|
||||||
ado_close_state: "Closed"
|
|
||||||
ado_bypassrules: true
|
|
||||||
log_level: 100
|
|
||||||
+3
-2
@@ -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.4.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: |
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// eslint-disable-next-line filenames/match-exported
|
||||||
const loaderUtils = require('loader-utils');
|
const loaderUtils = require('loader-utils');
|
||||||
|
|
||||||
module.exports = function cssVariablesLoader(source) {
|
module.exports = function cssVariablesLoader(source) {
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ class HistoryRow extends Component {
|
|||||||
movie,
|
movie,
|
||||||
quality,
|
quality,
|
||||||
customFormats,
|
customFormats,
|
||||||
|
customFormatScore,
|
||||||
languages,
|
languages,
|
||||||
qualityCutoffNotMet,
|
qualityCutoffNotMet,
|
||||||
eventType,
|
eventType,
|
||||||
@@ -175,7 +176,7 @@ class HistoryRow extends Component {
|
|||||||
key={name}
|
key={name}
|
||||||
className={styles.customFormatScore}
|
className={styles.customFormatScore}
|
||||||
>
|
>
|
||||||
{formatCustomFormatScore(data.customFormatScore)}
|
{formatCustomFormatScore(customFormatScore)}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -241,8 +242,9 @@ HistoryRow.propTypes = {
|
|||||||
movie: PropTypes.object.isRequired,
|
movie: PropTypes.object.isRequired,
|
||||||
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
|
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
quality: PropTypes.object.isRequired,
|
quality: PropTypes.object.isRequired,
|
||||||
|
customFormats: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
customFormatScore: PropTypes.number.isRequired,
|
||||||
qualityCutoffNotMet: PropTypes.bool.isRequired,
|
qualityCutoffNotMet: PropTypes.bool.isRequired,
|
||||||
customFormats: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
eventType: PropTypes.string.isRequired,
|
eventType: PropTypes.string.isRequired,
|
||||||
sourceTitle: PropTypes.string.isRequired,
|
sourceTitle: PropTypes.string.isRequired,
|
||||||
date: PropTypes.string.isRequired,
|
date: PropTypes.string.isRequired,
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ class QueueRow extends Component {
|
|||||||
|
|
||||||
{
|
{
|
||||||
columns.map((column) => {
|
columns.map((column) => {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
isVisible
|
isVisible
|
||||||
@@ -234,6 +235,16 @@ class QueueRow extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name === 'year') {
|
||||||
|
return (
|
||||||
|
<TableRowCell key={name}>
|
||||||
|
{
|
||||||
|
movie ? movie.year : ''
|
||||||
|
}
|
||||||
|
</TableRowCell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (name === 'title') {
|
if (name === 'title') {
|
||||||
return (
|
return (
|
||||||
<TableRowCell key={name}>
|
<TableRowCell key={name}>
|
||||||
@@ -362,6 +373,7 @@ QueueRow.propTypes = {
|
|||||||
estimatedCompletionTime: PropTypes.string,
|
estimatedCompletionTime: PropTypes.string,
|
||||||
timeleft: PropTypes.string,
|
timeleft: PropTypes.string,
|
||||||
size: PropTypes.number,
|
size: PropTypes.number,
|
||||||
|
year: PropTypes.number,
|
||||||
sizeleft: PropTypes.number,
|
sizeleft: PropTypes.number,
|
||||||
showRelativeDates: PropTypes.bool.isRequired,
|
showRelativeDates: PropTypes.bool.isRequired,
|
||||||
shortDateFormat: PropTypes.string.isRequired,
|
shortDateFormat: PropTypes.string.isRequired,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -257,10 +257,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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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) &&
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
$fullColorGradient: rgba(244, 245, 246, 0.2);
|
||||||
|
|
||||||
.event {
|
.event {
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
margin: 4px 2px;
|
margin: 4px 2px;
|
||||||
@@ -55,6 +57,10 @@
|
|||||||
.downloaded {
|
.downloaded {
|
||||||
border-left-color: var(--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(var(--successColor), saturation(+15%)) !important;
|
border-left-color: color(var(--successColor), saturation(+15%)) !important;
|
||||||
}
|
}
|
||||||
@@ -62,28 +68,72 @@
|
|||||||
|
|
||||||
.queue {
|
.queue {
|
||||||
border-left-color: var(--purple) !important;
|
border-left-color: var(--purple) !important;
|
||||||
|
|
||||||
|
&:global(.fullColor) {
|
||||||
|
background-color: rgba(122, 67, 182, 0.4) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.unmonitored {
|
.unmonitored {
|
||||||
border-left-color: var(--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: var(--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, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--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: var(--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, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--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: var(--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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
$fullColorGradient: rgba(244, 245, 246, 0.2);
|
||||||
|
|
||||||
.legendItemContainer {
|
.legendItemContainer {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
width: 220px;
|
width: 220px;
|
||||||
@@ -63,10 +65,12 @@
|
|||||||
|
|
||||||
.missingMonitoredColorImpaired {
|
.missingMonitoredColorImpaired {
|
||||||
background: repeating-linear-gradient(90deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--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, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px);
|
background: repeating-linear-gradient(45deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px);
|
||||||
|
color: var(--white);
|
||||||
}
|
}
|
||||||
|
|
||||||
.legendItemText {
|
.legendItemText {
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
@@ -162,7 +177,7 @@ class CalendarOptionsModalContent extends Component {
|
|||||||
values={weekColumnOptions}
|
values={weekColumnOptions}
|
||||||
value={calendarWeekColumnHeader}
|
value={calendarWeekColumnHeader}
|
||||||
onChange={this.onGlobalInputChange}
|
onChange={this.onGlobalInputChange}
|
||||||
helpText={translate('HelpText')}
|
helpText={translate('SettingsWeekColumnHeaderHelpText')}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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&';
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
.inputGroup {
|
.inputGroup {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputContainer {
|
.inputContainer {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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);
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/* eslint-disable no-bitwise */
|
/* eslint-disable no-bitwise */
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
import EnhancedSelectInput from './EnhancedSelectInput';
|
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||||
import styles from './UMaskInput.css';
|
import styles from './UMaskInput.css';
|
||||||
|
|
||||||
@@ -101,16 +102,16 @@ class UMaskInput extends Component {
|
|||||||
</div>
|
</div>
|
||||||
<div className={styles.details}>
|
<div className={styles.details}>
|
||||||
<div>
|
<div>
|
||||||
<label>UMask</label>
|
<label>{translate('UMask')}</label>
|
||||||
<div className={styles.value}>{umask}</div>
|
<div className={styles.value}>{umask}</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label>Folder</label>
|
<label>{translate('Folder')}</label>
|
||||||
<div className={styles.value}>{folder}</div>
|
<div className={styles.value}>{folder}</div>
|
||||||
<div className={styles.unit}>d{formatPermissions(folderNum)}</div>
|
<div className={styles.unit}>d{formatPermissions(folderNum)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label>File</label>
|
<label>{translate('File')}</label>
|
||||||
<div className={styles.value}>{file}</div>
|
<div className={styles.value}>{file}</div>
|
||||||
<div className={styles.unit}>{formatPermissions(fileNum)}</div>
|
<div className={styles.unit}>{formatPermissions(fileNum)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,10 +12,18 @@
|
|||||||
|
|
||||||
.info {
|
.info {
|
||||||
color: var(--infoColor);
|
color: var(--infoColor);
|
||||||
|
|
||||||
|
&:global(.darken) {
|
||||||
|
color: color(var(--infoColor) shade(30%));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pink {
|
.pink {
|
||||||
color: var(--pink);
|
color: var(--pink);
|
||||||
|
|
||||||
|
&:global(.darken) {
|
||||||
|
color: color(var(--pink) shade(30%));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.success {
|
.success {
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import styles from './PageHeaderActionsMenu.css';
|
|||||||
|
|
||||||
function PageHeaderActionsMenu(props) {
|
function PageHeaderActionsMenu(props) {
|
||||||
const {
|
const {
|
||||||
formsAuth,
|
cookieAuth,
|
||||||
onKeyboardShortcutsPress,
|
onKeyboardShortcutsPress,
|
||||||
onRestartPress,
|
onRestartPress,
|
||||||
onShutdownPress
|
onShutdownPress
|
||||||
@@ -56,22 +56,20 @@ function PageHeaderActionsMenu(props) {
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
||||||
{
|
{
|
||||||
formsAuth &&
|
cookieAuth &&
|
||||||
<div className={styles.separator} />
|
<>
|
||||||
}
|
<div className={styles.separator} />
|
||||||
|
<MenuItem
|
||||||
{
|
to={`${window.Radarr.urlBase}/logout?ReturnUrl=/`}
|
||||||
formsAuth &&
|
noRouter={true}
|
||||||
<MenuItem
|
>
|
||||||
to={`${window.Radarr.urlBase}/logout`}
|
<Icon
|
||||||
noRouter={true}
|
className={styles.itemIcon}
|
||||||
>
|
name={icons.LOGOUT}
|
||||||
<Icon
|
/>
|
||||||
className={styles.itemIcon}
|
Logout
|
||||||
name={icons.LOGOUT}
|
</MenuItem>
|
||||||
/>
|
</>
|
||||||
Logout
|
|
||||||
</MenuItem>
|
|
||||||
}
|
}
|
||||||
</MenuContent>
|
</MenuContent>
|
||||||
</Menu>
|
</Menu>
|
||||||
@@ -80,7 +78,7 @@ function PageHeaderActionsMenu(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PageHeaderActionsMenu.propTypes = {
|
PageHeaderActionsMenu.propTypes = {
|
||||||
formsAuth: PropTypes.bool.isRequired,
|
cookieAuth: PropTypes.bool.isRequired,
|
||||||
onKeyboardShortcutsPress: PropTypes.func.isRequired,
|
onKeyboardShortcutsPress: PropTypes.func.isRequired,
|
||||||
onRestartPress: PropTypes.func.isRequired,
|
onRestartPress: PropTypes.func.isRequired,
|
||||||
onShutdownPress: PropTypes.func.isRequired
|
onShutdownPress: PropTypes.func.isRequired
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ function createMapStateToProps() {
|
|||||||
(state) => state.system.status,
|
(state) => state.system.status,
|
||||||
(status) => {
|
(status) => {
|
||||||
return {
|
return {
|
||||||
formsAuth: status.item.authentication === 'forms'
|
cookieAuth: ['forms', 'oidc', 'plex'].includes(status.item.authentication)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import AppUpdatedModalConnector from 'App/AppUpdatedModalConnector';
|
|||||||
import ColorImpairedContext from 'App/ColorImpairedContext';
|
import ColorImpairedContext from 'App/ColorImpairedContext';
|
||||||
import ConnectionLostModalConnector from 'App/ConnectionLostModalConnector';
|
import ConnectionLostModalConnector from 'App/ConnectionLostModalConnector';
|
||||||
import SignalRConnector from 'Components/SignalRConnector';
|
import SignalRConnector from 'Components/SignalRConnector';
|
||||||
|
import AuthenticationRequiredModal from 'FirstRun/AuthenticationRequiredModal';
|
||||||
import locationShape from 'Helpers/Props/Shapes/locationShape';
|
import locationShape from 'Helpers/Props/Shapes/locationShape';
|
||||||
import PageHeader from './Header/PageHeader';
|
import PageHeader from './Header/PageHeader';
|
||||||
import PageSidebar from './Sidebar/PageSidebar';
|
import PageSidebar from './Sidebar/PageSidebar';
|
||||||
@@ -75,6 +76,7 @@ class Page extends Component {
|
|||||||
isSmallScreen,
|
isSmallScreen,
|
||||||
isSidebarVisible,
|
isSidebarVisible,
|
||||||
enableColorImpairedMode,
|
enableColorImpairedMode,
|
||||||
|
authenticationEnabled,
|
||||||
onSidebarToggle,
|
onSidebarToggle,
|
||||||
onSidebarVisibleChange
|
onSidebarVisibleChange
|
||||||
} = this.props;
|
} = this.props;
|
||||||
@@ -109,6 +111,10 @@ class Page extends Component {
|
|||||||
isOpen={this.state.isConnectionLostModalOpen}
|
isOpen={this.state.isConnectionLostModalOpen}
|
||||||
onModalClose={this.onConnectionLostModalClose}
|
onModalClose={this.onConnectionLostModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<AuthenticationRequiredModal
|
||||||
|
isOpen={!authenticationEnabled}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ColorImpairedContext.Provider>
|
</ColorImpairedContext.Provider>
|
||||||
);
|
);
|
||||||
@@ -124,6 +130,7 @@ Page.propTypes = {
|
|||||||
isUpdated: PropTypes.bool.isRequired,
|
isUpdated: PropTypes.bool.isRequired,
|
||||||
isDisconnected: PropTypes.bool.isRequired,
|
isDisconnected: PropTypes.bool.isRequired,
|
||||||
enableColorImpairedMode: PropTypes.bool.isRequired,
|
enableColorImpairedMode: PropTypes.bool.isRequired,
|
||||||
|
authenticationEnabled: PropTypes.bool.isRequired,
|
||||||
onResize: PropTypes.func.isRequired,
|
onResize: PropTypes.func.isRequired,
|
||||||
onSidebarToggle: PropTypes.func.isRequired,
|
onSidebarToggle: PropTypes.func.isRequired,
|
||||||
onSidebarVisibleChange: PropTypes.func.isRequired
|
onSidebarVisibleChange: PropTypes.func.isRequired
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { fetchImportLists, fetchIndexerFlags, fetchLanguages, fetchQualityProfil
|
|||||||
import { fetchStatus } from 'Store/Actions/systemActions';
|
import { fetchStatus } from 'Store/Actions/systemActions';
|
||||||
import { fetchTags } from 'Store/Actions/tagActions';
|
import { fetchTags } from 'Store/Actions/tagActions';
|
||||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||||
|
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
|
||||||
import ErrorPage from './ErrorPage';
|
import ErrorPage from './ErrorPage';
|
||||||
import LoadingPage from './LoadingPage';
|
import LoadingPage from './LoadingPage';
|
||||||
import Page from './Page';
|
import Page from './Page';
|
||||||
@@ -133,18 +134,21 @@ function createMapStateToProps() {
|
|||||||
selectErrors,
|
selectErrors,
|
||||||
selectAppProps,
|
selectAppProps,
|
||||||
createDimensionsSelector(),
|
createDimensionsSelector(),
|
||||||
|
createSystemStatusSelector(),
|
||||||
(
|
(
|
||||||
enableColorImpairedMode,
|
enableColorImpairedMode,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
errors,
|
errors,
|
||||||
app,
|
app,
|
||||||
dimensions
|
dimensions,
|
||||||
|
systemStatus
|
||||||
) => {
|
) => {
|
||||||
return {
|
return {
|
||||||
...app,
|
...app,
|
||||||
...errors,
|
...errors,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
isSmallScreen: dimensions.isSmallScreen,
|
isSmallScreen: dimensions.isSmallScreen,
|
||||||
|
authenticationEnabled: systemStatus.authentication !== 'none',
|
||||||
enableColorImpairedMode
|
enableColorImpairedMode
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: $breakpointLarge) {
|
@media only screen and (max-width: $breakpointExtraLarge) {
|
||||||
.contentFooter {
|
.contentFooter {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.tags {
|
||||||
|
flex: 1 0 auto;
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import { kinds } from 'Helpers/Props';
|
||||||
|
import Label from './Label';
|
||||||
|
import styles from './QualityProfileList.css';
|
||||||
|
|
||||||
|
function QualityProfileList({ qualityProfileIds, qualityProfileList }) {
|
||||||
|
return (
|
||||||
|
<div className={styles.tags}>
|
||||||
|
{
|
||||||
|
qualityProfileIds.map((t) => {
|
||||||
|
const qualityProfile = _.find(qualityProfileList, { id: t });
|
||||||
|
|
||||||
|
if (!qualityProfile) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Label
|
||||||
|
key={qualityProfile.id}
|
||||||
|
kind={kinds.INFO}
|
||||||
|
>
|
||||||
|
{qualityProfile.name}
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
QualityProfileList.propTypes = {
|
||||||
|
qualityProfileIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
|
qualityProfileList: PropTypes.arrayOf(PropTypes.object).isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default QualityProfileList;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import QualityProfileList from './QualityProfileList';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
(state) => state.settings.qualityProfiles.items,
|
||||||
|
(qualityProfileList) => {
|
||||||
|
return {
|
||||||
|
qualityProfileList
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps)(QualityProfileList);
|
||||||
@@ -73,7 +73,7 @@ function getInfoRowProps(row, props) {
|
|||||||
return {
|
return {
|
||||||
title: translate('Ratings'),
|
title: translate('Ratings'),
|
||||||
iconName: icons.HEART,
|
iconName: icons.HEART,
|
||||||
label: `${props.ratings.tmdb.value * 10}%`
|
label: `${(props.ratings.tmdb.value * 10).toFixed()}%`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import Modal from 'Components/Modal/Modal';
|
||||||
|
import { sizes } from 'Helpers/Props';
|
||||||
|
import AuthenticationRequiredModalContentConnector from './AuthenticationRequiredModalContentConnector';
|
||||||
|
|
||||||
|
function onModalClose() {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
|
||||||
|
function AuthenticationRequiredModal(props) {
|
||||||
|
const {
|
||||||
|
isOpen
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
size={sizes.MEDIUM}
|
||||||
|
isOpen={isOpen}
|
||||||
|
closeOnBackgroundClick={false}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
>
|
||||||
|
<AuthenticationRequiredModalContentConnector
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthenticationRequiredModal.propTypes = {
|
||||||
|
isOpen: PropTypes.bool.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthenticationRequiredModal;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
.authRequiredAlert {
|
||||||
|
composes: alert from '~Components/Alert.css';
|
||||||
|
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,267 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import Alert from 'Components/Alert';
|
||||||
|
import FormGroup from 'Components/Form/FormGroup';
|
||||||
|
import FormInputButton from 'Components/Form/FormInputButton';
|
||||||
|
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||||
|
import FormLabel from 'Components/Form/FormLabel';
|
||||||
|
import OAuthInputConnector from 'Components/Form/OAuthInputConnector';
|
||||||
|
import Icon from 'Components/Icon';
|
||||||
|
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||||
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
|
import { icons, inputTypes, kinds } from 'Helpers/Props';
|
||||||
|
import { authenticationMethodOptions, authenticationRequiredOptions, authenticationRequiredWarning } from 'Settings/General/SecuritySettings';
|
||||||
|
import styles from './AuthenticationRequiredModalContent.css';
|
||||||
|
|
||||||
|
const oauthData = {
|
||||||
|
implementation: { value: 'PlexImport' },
|
||||||
|
configContract: { value: 'PlexListSettings' },
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'textbox',
|
||||||
|
name: 'accessToken'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'oAuth',
|
||||||
|
name: 'signIn',
|
||||||
|
value: 'startAuth'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
function onModalClose() {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
|
||||||
|
function AuthenticationRequiredModalContent(props) {
|
||||||
|
const {
|
||||||
|
isPopulated,
|
||||||
|
plexServersPopulated,
|
||||||
|
error,
|
||||||
|
isSaving,
|
||||||
|
settings,
|
||||||
|
onInputChange,
|
||||||
|
onSavePress,
|
||||||
|
dispatchFetchStatus
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
authenticationMethod,
|
||||||
|
authenticationRequired,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
plexAuthServer,
|
||||||
|
plexRequireOwner,
|
||||||
|
oidcClientId,
|
||||||
|
oidcClientSecret,
|
||||||
|
oidcAuthority
|
||||||
|
} = settings;
|
||||||
|
|
||||||
|
const authenticationEnabled = authenticationMethod && authenticationMethod.value !== 'none';
|
||||||
|
const showUserPass = authenticationMethod && ['basic', 'forms'].includes(authenticationMethod.value);
|
||||||
|
const plexEnabled = authenticationMethod && authenticationMethod.value === 'plex';
|
||||||
|
const oidcEnabled = authenticationMethod && authenticationMethod.value === 'oidc';
|
||||||
|
|
||||||
|
const didMount = useRef(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isSaving && didMount.current) {
|
||||||
|
dispatchFetchStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
didMount.current = true;
|
||||||
|
}, [isSaving, dispatchFetchStatus]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContent
|
||||||
|
showCloseButton={false}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
>
|
||||||
|
<ModalHeader>
|
||||||
|
Authentication Required
|
||||||
|
</ModalHeader>
|
||||||
|
|
||||||
|
<ModalBody>
|
||||||
|
<Alert
|
||||||
|
className={styles.authRequiredAlert}
|
||||||
|
kind={kinds.WARNING}
|
||||||
|
>
|
||||||
|
{authenticationRequiredWarning}
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
{
|
||||||
|
isPopulated && !error ?
|
||||||
|
<div>
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>Authentication</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.SELECT}
|
||||||
|
name="authenticationMethod"
|
||||||
|
values={authenticationMethodOptions}
|
||||||
|
helpText="Require login to access Sonarr"
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...authenticationMethod}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
{
|
||||||
|
authenticationEnabled ?
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>Authentication Required</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.SELECT}
|
||||||
|
name="authenticationRequired"
|
||||||
|
values={authenticationRequiredOptions}
|
||||||
|
helpText="Change which requests authentication is required for. Do not change unless you understand the risks."
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...authenticationRequired}
|
||||||
|
/>
|
||||||
|
</FormGroup> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
showUserPass &&
|
||||||
|
<>
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>Username</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.TEXT}
|
||||||
|
name="username"
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...username}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>Password</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.PASSWORD}
|
||||||
|
name="password"
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...password}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
plexEnabled &&
|
||||||
|
<>
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>Plex Server</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.PLEX_MACHINE_SELECT}
|
||||||
|
name="plexAuthServer"
|
||||||
|
buttons={[
|
||||||
|
<FormInputButton
|
||||||
|
key="auth"
|
||||||
|
ButtonComponent={OAuthInputConnector}
|
||||||
|
label={plexServersPopulated ? <Icon name={icons.REFRESH} /> : 'Fetch'}
|
||||||
|
name="plexAuth"
|
||||||
|
provider="importList"
|
||||||
|
providerData={oauthData}
|
||||||
|
section="settings.importLists"
|
||||||
|
onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
]}
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...plexAuthServer}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>Restrict Access to Server Owner</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.CHECK}
|
||||||
|
name="plexRequireOwner"
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...plexRequireOwner}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
oidcEnabled &&
|
||||||
|
<>
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>Authority</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.TEXT}
|
||||||
|
name="oidcAuthority"
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...oidcAuthority}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>ClientId</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.TEXT}
|
||||||
|
name="oidcClientId"
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...oidcClientId}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>ClientSecret</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.PASSWORD}
|
||||||
|
name="oidcClientSecret"
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...oidcClientSecret}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</div> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
!isPopulated && !error ? <LoadingIndicator /> : null
|
||||||
|
}
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<SpinnerButton
|
||||||
|
kind={kinds.PRIMARY}
|
||||||
|
isSpinning={isSaving}
|
||||||
|
isDisabled={!authenticationEnabled}
|
||||||
|
onPress={onSavePress}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</SpinnerButton>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthenticationRequiredModalContent.propTypes = {
|
||||||
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
|
plexServersPopulated: PropTypes.bool.isRequired,
|
||||||
|
error: PropTypes.object,
|
||||||
|
isSaving: PropTypes.bool.isRequired,
|
||||||
|
saveError: PropTypes.object,
|
||||||
|
settings: PropTypes.object.isRequired,
|
||||||
|
onInputChange: PropTypes.func.isRequired,
|
||||||
|
onSavePress: PropTypes.func.isRequired,
|
||||||
|
dispatchFetchStatus: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthenticationRequiredModalContent;
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||||
|
import { fetchGeneralSettings, saveGeneralSettings, setGeneralSettingsValue } from 'Store/Actions/settingsActions';
|
||||||
|
import { fetchStatus } from 'Store/Actions/systemActions';
|
||||||
|
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
|
||||||
|
import AuthenticationRequiredModalContent from './AuthenticationRequiredModalContent';
|
||||||
|
|
||||||
|
const SECTION = 'general';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
createSettingsSectionSelector(SECTION),
|
||||||
|
(state) => state.settings.plex,
|
||||||
|
(sectionSettings, plex) => {
|
||||||
|
return {
|
||||||
|
...sectionSettings,
|
||||||
|
plexServersPopulated: plex.isPopulated
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
dispatchClearPendingChanges: clearPendingChanges,
|
||||||
|
dispatchSetGeneralSettingsValue: setGeneralSettingsValue,
|
||||||
|
dispatchSaveGeneralSettings: saveGeneralSettings,
|
||||||
|
dispatchFetchGeneralSettings: fetchGeneralSettings,
|
||||||
|
dispatchFetchStatus: fetchStatus
|
||||||
|
};
|
||||||
|
|
||||||
|
class AuthenticationRequiredModalContentConnector extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.dispatchFetchGeneralSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.props.dispatchClearPendingChanges({ section: `settings.${SECTION}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onInputChange = ({ name, value }) => {
|
||||||
|
this.props.dispatchSetGeneralSettingsValue({ name, value });
|
||||||
|
};
|
||||||
|
|
||||||
|
onSavePress = () => {
|
||||||
|
this.props.dispatchSaveGeneralSettings();
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
dispatchClearPendingChanges,
|
||||||
|
dispatchFetchGeneralSettings,
|
||||||
|
dispatchSetGeneralSettingsValue,
|
||||||
|
dispatchSaveGeneralSettings,
|
||||||
|
...otherProps
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthenticationRequiredModalContent
|
||||||
|
{...otherProps}
|
||||||
|
onInputChange={this.onInputChange}
|
||||||
|
onSavePress={this.onSavePress}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthenticationRequiredModalContentConnector.propTypes = {
|
||||||
|
dispatchClearPendingChanges: PropTypes.func.isRequired,
|
||||||
|
dispatchFetchGeneralSettings: PropTypes.func.isRequired,
|
||||||
|
dispatchSetGeneralSettingsValue: PropTypes.func.isRequired,
|
||||||
|
dispatchSaveGeneralSettings: PropTypes.func.isRequired,
|
||||||
|
dispatchFetchStatus: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps, mapDispatchToProps)(AuthenticationRequiredModalContentConnector);
|
||||||
@@ -47,6 +47,10 @@ export const possibleFilterTypes = {
|
|||||||
{ key: filterTypes.CONTAINS, value: 'contains' },
|
{ key: filterTypes.CONTAINS, value: 'contains' },
|
||||||
{ key: filterTypes.NOT_CONTAINS, value: 'does not contain' },
|
{ key: filterTypes.NOT_CONTAINS, value: 'does not contain' },
|
||||||
{ key: filterTypes.EQUAL, value: 'equal' },
|
{ key: filterTypes.EQUAL, value: 'equal' },
|
||||||
{ key: filterTypes.NOT_EQUAL, value: 'not equal' }
|
{ key: filterTypes.NOT_EQUAL, value: 'not equal' },
|
||||||
|
{ key: filterTypes.STARTS_WITH, value: 'starts with' },
|
||||||
|
{ key: filterTypes.NOT_STARTS_WITH, value: 'does not start with' },
|
||||||
|
{ key: filterTypes.ENDS_WITH, value: 'ends with' },
|
||||||
|
{ key: filterTypes.NOT_ENDS_WITH, value: 'does not end with' }
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -39,6 +39,22 @@ const filterTypePredicates = {
|
|||||||
|
|
||||||
[filterTypes.NOT_EQUAL]: function(itemValue, filterValue) {
|
[filterTypes.NOT_EQUAL]: function(itemValue, filterValue) {
|
||||||
return itemValue !== filterValue;
|
return itemValue !== filterValue;
|
||||||
|
},
|
||||||
|
|
||||||
|
[filterTypes.STARTS_WITH]: function(itemValue, filterValue) {
|
||||||
|
return itemValue.toLowerCase().startsWith(filterValue.toLowerCase());
|
||||||
|
},
|
||||||
|
|
||||||
|
[filterTypes.NOT_STARTS_WITH]: function(itemValue, filterValue) {
|
||||||
|
return !itemValue.toLowerCase().startsWith(filterValue.toLowerCase());
|
||||||
|
},
|
||||||
|
|
||||||
|
[filterTypes.ENDS_WITH]: function(itemValue, filterValue) {
|
||||||
|
return itemValue.toLowerCase().endsWith(filterValue.toLowerCase());
|
||||||
|
},
|
||||||
|
|
||||||
|
[filterTypes.NOT_ENDS_WITH]: function(itemValue, filterValue) {
|
||||||
|
return !itemValue.toLowerCase().endsWith(filterValue.toLowerCase());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ export const LESS_THAN = 'lessThan';
|
|||||||
export const LESS_THAN_OR_EQUAL = 'lessThanOrEqual';
|
export const LESS_THAN_OR_EQUAL = 'lessThanOrEqual';
|
||||||
export const NOT_CONTAINS = 'notContains';
|
export const NOT_CONTAINS = 'notContains';
|
||||||
export const NOT_EQUAL = 'notEqual';
|
export const NOT_EQUAL = 'notEqual';
|
||||||
|
export const STARTS_WITH = 'startsWith';
|
||||||
|
export const NOT_STARTS_WITH = 'notStartsWith';
|
||||||
|
export const ENDS_WITH = 'endsWith';
|
||||||
|
export const NOT_ENDS_WITH = 'notEndsWith';
|
||||||
|
|
||||||
export const all = [
|
export const all = [
|
||||||
CONTAINS,
|
CONTAINS,
|
||||||
@@ -23,5 +27,9 @@ export const all = [
|
|||||||
IN_LAST,
|
IN_LAST,
|
||||||
NOT_IN_LAST,
|
NOT_IN_LAST,
|
||||||
IN_NEXT,
|
IN_NEXT,
|
||||||
NOT_IN_NEXT
|
NOT_IN_NEXT,
|
||||||
|
STARTS_WITH,
|
||||||
|
NOT_STARTS_WITH,
|
||||||
|
ENDS_WITH,
|
||||||
|
NOT_ENDS_WITH
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export const NUMBER = 'number';
|
|||||||
export const OAUTH = 'oauth';
|
export const OAUTH = 'oauth';
|
||||||
export const PASSWORD = 'password';
|
export const PASSWORD = 'password';
|
||||||
export const PATH = 'path';
|
export const PATH = 'path';
|
||||||
|
export const PLEX_MACHINE_SELECT = 'plexMachineSelect';
|
||||||
export const QUALITY_PROFILE_SELECT = 'qualityProfileSelect';
|
export const QUALITY_PROFILE_SELECT = 'qualityProfileSelect';
|
||||||
export const ROOT_FOLDER_SELECT = 'rootFolderSelect';
|
export const ROOT_FOLDER_SELECT = 'rootFolderSelect';
|
||||||
export const INDEXER_FLAGS_SELECT = 'indexerFlagsSelect';
|
export const INDEXER_FLAGS_SELECT = 'indexerFlagsSelect';
|
||||||
@@ -35,6 +36,7 @@ export const all = [
|
|||||||
OAUTH,
|
OAUTH,
|
||||||
PASSWORD,
|
PASSWORD,
|
||||||
PATH,
|
PATH,
|
||||||
|
PLEX_MACHINE_SELECT,
|
||||||
QUALITY_PROFILE_SELECT,
|
QUALITY_PROFILE_SELECT,
|
||||||
DOWNLOAD_CLIENT_SELECT,
|
DOWNLOAD_CLIENT_SELECT,
|
||||||
ROOT_FOLDER_SELECT,
|
ROOT_FOLDER_SELECT,
|
||||||
|
|||||||
@@ -64,6 +64,15 @@ const columns = [
|
|||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'customFormats',
|
||||||
|
label: React.createElement(Icon, {
|
||||||
|
name: icons.INTERACTIVE,
|
||||||
|
title: translate('CustomFormat')
|
||||||
|
}),
|
||||||
|
isSortable: true,
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'rejections',
|
name: 'rejections',
|
||||||
label: React.createElement(Icon, {
|
label: React.createElement(Icon, {
|
||||||
|
|||||||
@@ -5,8 +5,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.quality,
|
.quality,
|
||||||
.language {
|
.languages {
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
@@ -21,3 +23,7 @@
|
|||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
text-align: start;
|
text-align: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.customFormatTooltip {
|
||||||
|
max-width: 250px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal'
|
|||||||
import SelectMovieModal from 'InteractiveImport/Movie/SelectMovieModal';
|
import SelectMovieModal from 'InteractiveImport/Movie/SelectMovieModal';
|
||||||
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
|
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
|
||||||
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
|
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
|
||||||
|
import MovieFormats from 'Movie/MovieFormats';
|
||||||
import MovieLanguage from 'Movie/MovieLanguage';
|
import MovieLanguage from 'Movie/MovieLanguage';
|
||||||
import MovieQuality from 'Movie/MovieQuality';
|
import MovieQuality from 'Movie/MovieQuality';
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
@@ -150,6 +151,7 @@ class InteractiveImportRow extends Component {
|
|||||||
languages,
|
languages,
|
||||||
releaseGroup,
|
releaseGroup,
|
||||||
size,
|
size,
|
||||||
|
customFormats,
|
||||||
rejections,
|
rejections,
|
||||||
isReprocessing,
|
isReprocessing,
|
||||||
isSelected,
|
isSelected,
|
||||||
@@ -226,7 +228,7 @@ class InteractiveImportRow extends Component {
|
|||||||
</TableRowCellButton>
|
</TableRowCellButton>
|
||||||
|
|
||||||
<TableRowCellButton
|
<TableRowCellButton
|
||||||
className={styles.language}
|
className={styles.languages}
|
||||||
title={translate('ClickToChangeLanguage')}
|
title={translate('ClickToChangeLanguage')}
|
||||||
onPress={this.onSelectLanguagePress}
|
onPress={this.onSelectLanguagePress}
|
||||||
>
|
>
|
||||||
@@ -259,7 +261,26 @@ class InteractiveImportRow extends Component {
|
|||||||
|
|
||||||
<TableRowCell>
|
<TableRowCell>
|
||||||
{
|
{
|
||||||
!!rejections.length &&
|
customFormats?.length ?
|
||||||
|
<Popover
|
||||||
|
anchor={
|
||||||
|
<Icon name={icons.INTERACTIVE} />
|
||||||
|
}
|
||||||
|
title={translate('Formats')}
|
||||||
|
body={
|
||||||
|
<div className={styles.customFormatTooltip}>
|
||||||
|
<MovieFormats formats={customFormats} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
position={tooltipPositions.LEFT}
|
||||||
|
/> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell>
|
||||||
|
{
|
||||||
|
rejections.length ?
|
||||||
<Popover
|
<Popover
|
||||||
anchor={
|
anchor={
|
||||||
<Icon
|
<Icon
|
||||||
@@ -282,7 +303,9 @@ class InteractiveImportRow extends Component {
|
|||||||
</ul>
|
</ul>
|
||||||
}
|
}
|
||||||
position={tooltipPositions.LEFT}
|
position={tooltipPositions.LEFT}
|
||||||
/>
|
canFlip={false}
|
||||||
|
/> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
@@ -330,6 +353,7 @@ InteractiveImportRow.propTypes = {
|
|||||||
languages: PropTypes.arrayOf(PropTypes.object),
|
languages: PropTypes.arrayOf(PropTypes.object),
|
||||||
releaseGroup: PropTypes.string,
|
releaseGroup: PropTypes.string,
|
||||||
size: PropTypes.number.isRequired,
|
size: PropTypes.number.isRequired,
|
||||||
|
customFormats: PropTypes.arrayOf(PropTypes.object),
|
||||||
rejections: PropTypes.arrayOf(PropTypes.object).isRequired,
|
rejections: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
isReprocessing: PropTypes.bool,
|
isReprocessing: PropTypes.bool,
|
||||||
isSelected: PropTypes.bool,
|
isSelected: PropTypes.bool,
|
||||||
|
|||||||
+62
-34
@@ -2,12 +2,15 @@ import PropTypes from 'prop-types';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||||
|
import PageMenuButton from 'Components/Menu/PageMenuButton';
|
||||||
import Table from 'Components/Table/Table';
|
import Table from 'Components/Table/Table';
|
||||||
import TableBody from 'Components/Table/TableBody';
|
import TableBody from 'Components/Table/TableBody';
|
||||||
import { icons, sortDirections } from 'Helpers/Props';
|
import { align, icons, sortDirections } from 'Helpers/Props';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
|
import InteractiveSearchFilterModalConnector from './InteractiveSearchFilterModalConnector';
|
||||||
import InteractiveSearchRowConnector from './InteractiveSearchRowConnector';
|
import InteractiveSearchRowConnector from './InteractiveSearchRowConnector';
|
||||||
import styles from './InteractiveSearchContent.css';
|
import styles from './InteractiveSearch.css';
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@@ -22,20 +25,6 @@ const columns = [
|
|||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'releaseWeight',
|
|
||||||
label: React.createElement(Icon, { name: icons.DOWNLOAD }),
|
|
||||||
isSortable: true,
|
|
||||||
fixedSortDirection: sortDirections.ASCENDING,
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'rejections',
|
|
||||||
label: React.createElement(Icon, { name: icons.DANGER }),
|
|
||||||
isSortable: true,
|
|
||||||
fixedSortDirection: sortDirections.ASCENDING,
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'title',
|
name: 'title',
|
||||||
label: translate('Title'),
|
label: translate('Title'),
|
||||||
@@ -99,10 +88,24 @@ const columns = [
|
|||||||
label: React.createElement(Icon, { name: icons.FLAG }),
|
label: React.createElement(Icon, { name: icons.FLAG }),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'rejections',
|
||||||
|
label: React.createElement(Icon, { name: icons.DANGER }),
|
||||||
|
isSortable: true,
|
||||||
|
fixedSortDirection: sortDirections.ASCENDING,
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'releaseWeight',
|
||||||
|
label: React.createElement(Icon, { name: icons.DOWNLOAD }),
|
||||||
|
isSortable: true,
|
||||||
|
fixedSortDirection: sortDirections.ASCENDING,
|
||||||
|
isVisible: true
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
function InteractiveSearchContent(props) {
|
function InteractiveSearch(props) {
|
||||||
const {
|
const {
|
||||||
searchPayload,
|
searchPayload,
|
||||||
isFetching,
|
isFetching,
|
||||||
@@ -110,44 +113,63 @@ function InteractiveSearchContent(props) {
|
|||||||
error,
|
error,
|
||||||
totalReleasesCount,
|
totalReleasesCount,
|
||||||
items,
|
items,
|
||||||
|
selectedFilterKey,
|
||||||
|
filters,
|
||||||
|
customFilters,
|
||||||
sortKey,
|
sortKey,
|
||||||
sortDirection,
|
sortDirection,
|
||||||
longDateFormat,
|
longDateFormat,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
onSortPress,
|
onSortPress,
|
||||||
|
onFilterSelect,
|
||||||
onGrabPress
|
onGrabPress
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<div className={styles.filterMenuContainer}>
|
||||||
|
<FilterMenu
|
||||||
|
alignMenu={align.RIGHT}
|
||||||
|
selectedFilterKey={selectedFilterKey}
|
||||||
|
filters={filters}
|
||||||
|
customFilters={customFilters}
|
||||||
|
buttonComponent={PageMenuButton}
|
||||||
|
filterModalConnectorComponent={InteractiveSearchFilterModalConnector}
|
||||||
|
filterModalConnectorComponentProps={'movies'}
|
||||||
|
onFilterSelect={onFilterSelect}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
isFetching &&
|
isFetching ? <LoadingIndicator /> : null
|
||||||
<LoadingIndicator />
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!isFetching && !!error &&
|
!isFetching && error ?
|
||||||
<div className={styles.blankpad}>
|
<div>
|
||||||
{translate('UnableToLoadResultsIntSearch')}
|
{translate('UnableToLoadResultsIntSearch')}
|
||||||
</div>
|
</div> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!isFetching && isPopulated && !totalReleasesCount &&
|
!isFetching && isPopulated && !totalReleasesCount ?
|
||||||
<div className={styles.blankpad}>
|
<div>
|
||||||
{translate('NoResultsFound')}
|
{translate('NoResultsFound')}
|
||||||
</div>
|
</div> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!!totalReleasesCount && isPopulated && !items.length &&
|
!!totalReleasesCount && isPopulated && !items.length ?
|
||||||
<div className={styles.blankpad}>
|
<div>
|
||||||
{translate('AllResultsHiddenFilter')}
|
{translate('AllResultsHiddenFilter')}
|
||||||
</div>
|
</div> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
isPopulated && !!items.length &&
|
isPopulated && !!items.length ?
|
||||||
<Table
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
sortKey={sortKey}
|
sortKey={sortKey}
|
||||||
@@ -170,32 +192,38 @@ function InteractiveSearchContent(props) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
totalReleasesCount !== items.length && !!items.length &&
|
totalReleasesCount !== items.length && !!items.length ?
|
||||||
<div className={styles.filteredMessage}>
|
<div className={styles.filteredMessage}>
|
||||||
{translate('SomeResultsHiddenFilter')}
|
{translate('SomeResultsHiddenFilter')}
|
||||||
</div>
|
</div> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
InteractiveSearchContent.propTypes = {
|
InteractiveSearch.propTypes = {
|
||||||
searchPayload: PropTypes.object.isRequired,
|
searchPayload: PropTypes.object.isRequired,
|
||||||
isFetching: PropTypes.bool.isRequired,
|
isFetching: PropTypes.bool.isRequired,
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
error: PropTypes.object,
|
error: PropTypes.object,
|
||||||
totalReleasesCount: PropTypes.number.isRequired,
|
totalReleasesCount: PropTypes.number.isRequired,
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||||
|
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
sortKey: PropTypes.string,
|
sortKey: PropTypes.string,
|
||||||
sortDirection: PropTypes.string,
|
sortDirection: PropTypes.string,
|
||||||
longDateFormat: PropTypes.string.isRequired,
|
longDateFormat: PropTypes.string.isRequired,
|
||||||
timeFormat: PropTypes.string.isRequired,
|
timeFormat: PropTypes.string.isRequired,
|
||||||
onSortPress: PropTypes.func.isRequired,
|
onSortPress: PropTypes.func.isRequired,
|
||||||
|
onFilterSelect: PropTypes.func.isRequired,
|
||||||
onGrabPress: PropTypes.func.isRequired
|
onGrabPress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default InteractiveSearchContent;
|
export default InteractiveSearch;
|
||||||
+5
-5
@@ -5,7 +5,7 @@ import { createSelector } from 'reselect';
|
|||||||
import * as releaseActions from 'Store/Actions/releaseActions';
|
import * as releaseActions from 'Store/Actions/releaseActions';
|
||||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||||
import InteractiveSearchContent from './InteractiveSearchContent';
|
import InteractiveSearch from './InteractiveSearch';
|
||||||
|
|
||||||
function createMapStateToProps(appState) {
|
function createMapStateToProps(appState) {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
@@ -48,7 +48,7 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
class InteractiveSearchContentConnector extends Component {
|
class InteractiveSearchConnector extends Component {
|
||||||
|
|
||||||
//
|
//
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
@@ -79,18 +79,18 @@ class InteractiveSearchContentConnector extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<InteractiveSearchContent
|
<InteractiveSearch
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InteractiveSearchContentConnector.propTypes = {
|
InteractiveSearchConnector.propTypes = {
|
||||||
searchPayload: PropTypes.object.isRequired,
|
searchPayload: PropTypes.object.isRequired,
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
dispatchFetchReleases: PropTypes.func.isRequired,
|
dispatchFetchReleases: PropTypes.func.isRequired,
|
||||||
dispatchClearReleases: PropTypes.func.isRequired
|
dispatchClearReleases: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(createMapStateToProps, createMapDispatchToProps)(InteractiveSearchContentConnector);
|
export default connect(createMapStateToProps, createMapDispatchToProps)(InteractiveSearchConnector);
|
||||||
@@ -1,15 +1,20 @@
|
|||||||
.cell {
|
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
|
||||||
}
|
|
||||||
|
|
||||||
.protocol {
|
.protocol {
|
||||||
composes: cell;
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 80px;
|
width: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
.indexer {
|
.indexer {
|
||||||
composes: cell;
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 85px;
|
width: 85px;
|
||||||
}
|
}
|
||||||
@@ -17,7 +22,9 @@
|
|||||||
.quality,
|
.quality,
|
||||||
.customFormat,
|
.customFormat,
|
||||||
.language {
|
.language {
|
||||||
composes: cell;
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.language {
|
.language {
|
||||||
@@ -25,7 +32,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.customFormatScore {
|
.customFormatScore {
|
||||||
composes: cell;
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 55px;
|
width: 55px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -35,34 +42,26 @@
|
|||||||
.rejected,
|
.rejected,
|
||||||
.indexerFlags,
|
.indexerFlags,
|
||||||
.download {
|
.download {
|
||||||
composes: cell;
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 50px;
|
width: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.age,
|
.age,
|
||||||
.size {
|
.size {
|
||||||
composes: cell;
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.peers {
|
.peers {
|
||||||
composes: cell;
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 75px;
|
width: 75px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
|
||||||
composes: cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title div {
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history {
|
.history {
|
||||||
composes: cell;
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 75px;
|
width: 75px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,46 +145,6 @@ class InteractiveSearchRow extends Component {
|
|||||||
{formatAge(age, ageHours, ageMinutes)}
|
{formatAge(age, ageHours, ageMinutes)}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
<TableRowCell className={styles.download}>
|
|
||||||
<SpinnerIconButton
|
|
||||||
name={getDownloadIcon(isGrabbing, isGrabbed, grabError)}
|
|
||||||
kind={grabError ? kinds.DANGER : kinds.DEFAULT}
|
|
||||||
title={getDownloadTooltip(isGrabbing, isGrabbed, grabError)}
|
|
||||||
isDisabled={isGrabbed}
|
|
||||||
isSpinning={isGrabbing}
|
|
||||||
onPress={downloadAllowed ? this.onGrabPress : this.onConfirmGrabPress}
|
|
||||||
/>
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell className={styles.rejected}>
|
|
||||||
{
|
|
||||||
!!rejections.length &&
|
|
||||||
<Popover
|
|
||||||
anchor={
|
|
||||||
<Icon
|
|
||||||
name={icons.DANGER}
|
|
||||||
kind={kinds.DANGER}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
title={translate('ReleaseRejected')}
|
|
||||||
body={
|
|
||||||
<ul>
|
|
||||||
{
|
|
||||||
rejections.map((rejection, index) => {
|
|
||||||
return (
|
|
||||||
<li key={index}>
|
|
||||||
{rejection}
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
}
|
|
||||||
position={tooltipPositions.BOTTOM}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell className={styles.title}>
|
<TableRowCell className={styles.title}>
|
||||||
<Link
|
<Link
|
||||||
to={infoUrl}
|
to={infoUrl}
|
||||||
@@ -297,6 +257,46 @@ class InteractiveSearchRow extends Component {
|
|||||||
}
|
}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell className={styles.rejected}>
|
||||||
|
{
|
||||||
|
!!rejections.length &&
|
||||||
|
<Popover
|
||||||
|
anchor={
|
||||||
|
<Icon
|
||||||
|
name={icons.DANGER}
|
||||||
|
kind={kinds.DANGER}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
title={translate('ReleaseRejected')}
|
||||||
|
body={
|
||||||
|
<ul>
|
||||||
|
{
|
||||||
|
rejections.map((rejection, index) => {
|
||||||
|
return (
|
||||||
|
<li key={index}>
|
||||||
|
{rejection}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
position={tooltipPositions.LEFT}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell className={styles.download}>
|
||||||
|
<SpinnerIconButton
|
||||||
|
name={getDownloadIcon(isGrabbing, isGrabbed, grabError)}
|
||||||
|
kind={grabError ? kinds.DANGER : kinds.DEFAULT}
|
||||||
|
title={getDownloadTooltip(isGrabbing, isGrabbed, grabError)}
|
||||||
|
isDisabled={isGrabbed}
|
||||||
|
isSpinning={isGrabbing}
|
||||||
|
onPress={downloadAllowed ? this.onGrabPress : this.onConfirmGrabPress}
|
||||||
|
/>
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
isOpen={this.state.isConfirmGrabModalOpen}
|
isOpen={this.state.isConfirmGrabModalOpen}
|
||||||
kind={kinds.WARNING}
|
kind={kinds.WARNING}
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import InteractiveSearchContentConnector from './InteractiveSearchContentConnector';
|
|
||||||
|
|
||||||
function InteractiveSearchTable(props) {
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InteractiveSearchContentConnector
|
|
||||||
searchPayload={props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
InteractiveSearchTable.propTypes = {
|
|
||||||
};
|
|
||||||
|
|
||||||
export default InteractiveSearchTable;
|
|
||||||
@@ -54,18 +54,22 @@ class DeleteMovieModalContent extends Component {
|
|||||||
const {
|
const {
|
||||||
title,
|
title,
|
||||||
path,
|
path,
|
||||||
hasFile,
|
statistics,
|
||||||
sizeOnDisk,
|
|
||||||
onModalClose
|
onModalClose
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
sizeOnDisk,
|
||||||
|
movieFileCount
|
||||||
|
} = statistics;
|
||||||
|
|
||||||
const deleteFiles = this.state.deleteFiles;
|
const deleteFiles = this.state.deleteFiles;
|
||||||
const addImportExclusion = this.state.addImportExclusion;
|
const addImportExclusion = this.state.addImportExclusion;
|
||||||
|
|
||||||
let deleteFilesLabel = hasFile ? translate('DeleteFileLabel', [1]) : translate('DeleteFilesLabel', [0]);
|
let deleteFilesLabel = movieFileCount === 1 ? translate('DeleteFileLabel', [1]) : translate('DeleteFilesLabel', [movieFileCount]);
|
||||||
let deleteFilesHelpText = translate('DeleteFilesHelpText');
|
let deleteFilesHelpText = translate('DeleteFilesHelpText');
|
||||||
|
|
||||||
if (!hasFile) {
|
if (movieFileCount === 0) {
|
||||||
deleteFilesLabel = translate('DeleteMovieFolderLabel');
|
deleteFilesLabel = translate('DeleteMovieFolderLabel');
|
||||||
deleteFilesHelpText = translate('DeleteMovieFolderHelpText');
|
deleteFilesHelpText = translate('DeleteMovieFolderHelpText');
|
||||||
}
|
}
|
||||||
@@ -124,9 +128,9 @@ class DeleteMovieModalContent extends Component {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
!!hasFile &&
|
movieFileCount > 0 &&
|
||||||
<div>
|
<div>
|
||||||
{hasFile} {translate('MovieFilesTotaling')} {formatBytes(sizeOnDisk)}
|
{movieFileCount} {translate('MovieFilesTotaling')} {formatBytes(sizeOnDisk)}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -154,10 +158,16 @@ class DeleteMovieModalContent extends Component {
|
|||||||
DeleteMovieModalContent.propTypes = {
|
DeleteMovieModalContent.propTypes = {
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
path: PropTypes.string.isRequired,
|
path: PropTypes.string.isRequired,
|
||||||
hasFile: PropTypes.bool.isRequired,
|
statistics: PropTypes.object.isRequired,
|
||||||
sizeOnDisk: PropTypes.number.isRequired,
|
|
||||||
onDeletePress: PropTypes.func.isRequired,
|
onDeletePress: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
DeleteMovieModalContent.defaultProps = {
|
||||||
|
statistics: {
|
||||||
|
sizeOnDisk: 0,
|
||||||
|
movieFileCount: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default DeleteMovieModalContent;
|
export default DeleteMovieModalContent;
|
||||||
|
|||||||
@@ -69,7 +69,8 @@ class MovieCastPoster extends Component {
|
|||||||
|
|
||||||
const elementStyle = {
|
const elementStyle = {
|
||||||
width: `${posterWidth}px`,
|
width: `${posterWidth}px`,
|
||||||
height: `${posterHeight}px`
|
height: `${posterHeight}px`,
|
||||||
|
borderRadius: '5px'
|
||||||
};
|
};
|
||||||
|
|
||||||
const contentStyle = {
|
const contentStyle = {
|
||||||
|
|||||||
@@ -69,7 +69,8 @@ class MovieCrewPoster extends Component {
|
|||||||
|
|
||||||
const elementStyle = {
|
const elementStyle = {
|
||||||
width: `${posterWidth}px`,
|
width: `${posterWidth}px`,
|
||||||
height: `${posterHeight}px`
|
height: `${posterHeight}px`,
|
||||||
|
borderRadius: '5px'
|
||||||
};
|
};
|
||||||
|
|
||||||
const contentStyle = {
|
const contentStyle = {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
$hoverScale: 1.05;
|
$hoverScale: 1.05;
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
border-radius: 5px;
|
||||||
transition: all 200ms ease-in;
|
transition: all 200ms ease-in;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
flex: 1 0 auto;
|
flex: 1 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.movie {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { Grid, WindowScroller } from 'react-virtualized';
|
import { Navigation } from 'swiper';
|
||||||
import Measure from 'Components/Measure';
|
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||||
import dimensions from 'Styles/Variables/dimensions';
|
import dimensions from 'Styles/Variables/dimensions';
|
||||||
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
|
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
|
||||||
import MovieCreditPosterConnector from './MovieCreditPosterConnector';
|
import MovieCreditPosterConnector from './MovieCreditPosterConnector';
|
||||||
import styles from './MovieCreditPosters.css';
|
import styles from './MovieCreditPosters.css';
|
||||||
|
|
||||||
|
// Import Swiper styles
|
||||||
|
import 'swiper/css';
|
||||||
|
import 'swiper/css/navigation';
|
||||||
|
|
||||||
// Poster container dimensions
|
// Poster container dimensions
|
||||||
const columnPadding = parseInt(dimensions.movieIndexColumnPadding);
|
const columnPadding = parseInt(dimensions.movieIndexColumnPadding);
|
||||||
const columnPaddingSmallScreen = parseInt(dimensions.movieIndexColumnPaddingSmallScreen);
|
const columnPaddingSmallScreen = parseInt(dimensions.movieIndexColumnPaddingSmallScreen);
|
||||||
@@ -169,56 +173,50 @@ class MovieCreditPosters extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
items
|
items,
|
||||||
|
itemComponent
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
width,
|
posterWidth,
|
||||||
columnWidth,
|
posterHeight
|
||||||
columnCount,
|
|
||||||
rowHeight
|
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const rowCount = Math.ceil(items.length / columnCount);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Measure
|
|
||||||
whitelist={['width']}
|
|
||||||
onMeasure={this.onMeasure}
|
|
||||||
>
|
|
||||||
<WindowScroller
|
|
||||||
scrollElement={undefined}
|
|
||||||
>
|
|
||||||
{({ height, registerChild, onChildScroll, scrollTop }) => {
|
|
||||||
if (!height) {
|
|
||||||
return <div />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
<div className={styles.sliderContainer}>
|
||||||
<div ref={registerChild}>
|
<Swiper
|
||||||
<Grid
|
slidesPerView='auto'
|
||||||
ref={this.setGridRef}
|
spaceBetween={10}
|
||||||
className={styles.grid}
|
slidesPerGroup={3}
|
||||||
autoHeight={true}
|
loop={false}
|
||||||
height={height}
|
loopFillGroupWithBlank={true}
|
||||||
columnCount={columnCount}
|
className="mySwiper"
|
||||||
columnWidth={columnWidth}
|
modules={[Navigation]}
|
||||||
rowCount={rowCount}
|
onInit={(swiper) => {
|
||||||
rowHeight={rowHeight}
|
swiper.params.navigation.prevEl = this._swiperPrevRef;
|
||||||
width={width}
|
swiper.params.navigation.nextEl = this._swiperNextRef;
|
||||||
onScroll={onChildScroll}
|
swiper.navigation.init();
|
||||||
scrollTop={scrollTop}
|
swiper.navigation.update();
|
||||||
overscanRowCount={2}
|
}}
|
||||||
cellRenderer={this.cellRenderer}
|
>
|
||||||
scrollToAlignment={'start'}
|
{items.map((credit) => (
|
||||||
isScrollingOptOut={true}
|
<SwiperSlide key={credit.tmdbId} style={{ width: posterWidth }}>
|
||||||
/>
|
<MovieCreditPosterConnector
|
||||||
</div>
|
key={credit.order}
|
||||||
);
|
component={itemComponent}
|
||||||
}
|
posterWidth={posterWidth}
|
||||||
}
|
posterHeight={posterHeight}
|
||||||
</WindowScroller>
|
tmdbId={credit.personTmdbId}
|
||||||
</Measure>
|
personName={credit.personName}
|
||||||
|
job={credit.job}
|
||||||
|
character={credit.character}
|
||||||
|
images={credit.images}
|
||||||
|
/>
|
||||||
|
</SwiperSlide>
|
||||||
|
))}
|
||||||
|
</Swiper>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
.alternateTitle {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import styles from './MovieAlternateTitles.css';
|
|
||||||
|
|
||||||
function MovieAlternateTitles({ alternateTitles }) {
|
|
||||||
return (
|
|
||||||
<ul>
|
|
||||||
{
|
|
||||||
alternateTitles.filter((x, i, a) => a.indexOf(x) === i).map((alternateTitle) => {
|
|
||||||
return (
|
|
||||||
<li
|
|
||||||
key={alternateTitle}
|
|
||||||
className={styles.alternateTitle}
|
|
||||||
>
|
|
||||||
{alternateTitle}
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
MovieAlternateTitles.propTypes = {
|
|
||||||
alternateTitles: PropTypes.arrayOf(PropTypes.string).isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MovieAlternateTitles;
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
.header {
|
.header {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 375px;
|
height: 425px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.errorMessage {
|
.errorMessage {
|
||||||
@@ -39,10 +39,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.poster {
|
.poster {
|
||||||
|
z-index: 2;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-right: 35px;
|
margin-right: 35px;
|
||||||
width: 217px;
|
width: 250px;
|
||||||
height: 319px;
|
height: 368px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
|
|
||||||
import TextTruncate from 'react-text-truncate';
|
import TextTruncate from 'react-text-truncate';
|
||||||
|
import FieldSet from 'Components/FieldSet';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import ImdbRating from 'Components/ImdbRating';
|
import ImdbRating from 'Components/ImdbRating';
|
||||||
import InfoLabel from 'Components/InfoLabel';
|
import InfoLabel from 'Components/InfoLabel';
|
||||||
@@ -22,12 +22,11 @@ import Popover from 'Components/Tooltip/Popover';
|
|||||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||||
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
|
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
|
||||||
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
||||||
import InteractiveSearchFilterMenuConnector from 'InteractiveSearch/InteractiveSearchFilterMenuConnector';
|
|
||||||
import InteractiveSearchTable from 'InteractiveSearch/InteractiveSearchTable';
|
|
||||||
import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal';
|
import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal';
|
||||||
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
||||||
import MovieHistoryTable from 'Movie/History/MovieHistoryTable';
|
import MovieHistoryTable from 'Movie/History/MovieHistoryTable';
|
||||||
import MoviePoster from 'Movie/MoviePoster';
|
import MoviePoster from 'Movie/MoviePoster';
|
||||||
|
import MovieInteractiveSearchModalConnector from 'Movie/Search/MovieInteractiveSearchModalConnector';
|
||||||
import MovieFileEditorTable from 'MovieFile/Editor/MovieFileEditorTable';
|
import MovieFileEditorTable from 'MovieFile/Editor/MovieFileEditorTable';
|
||||||
import ExtraFileTable from 'MovieFile/Extras/ExtraFileTable';
|
import ExtraFileTable from 'MovieFile/Extras/ExtraFileTable';
|
||||||
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
|
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
|
||||||
@@ -81,10 +80,10 @@ class MovieDetails extends Component {
|
|||||||
isEditMovieModalOpen: false,
|
isEditMovieModalOpen: false,
|
||||||
isDeleteMovieModalOpen: false,
|
isDeleteMovieModalOpen: false,
|
||||||
isInteractiveImportModalOpen: false,
|
isInteractiveImportModalOpen: false,
|
||||||
|
isInteractiveSearchModalOpen: false,
|
||||||
allExpanded: false,
|
allExpanded: false,
|
||||||
allCollapsed: false,
|
allCollapsed: false,
|
||||||
expandedState: {},
|
expandedState: {},
|
||||||
selectedTabIndex: 0,
|
|
||||||
overviewHeight: 0,
|
overviewHeight: 0,
|
||||||
titleWidth: 0
|
titleWidth: 0
|
||||||
};
|
};
|
||||||
@@ -137,6 +136,14 @@ class MovieDetails extends Component {
|
|||||||
this.setState({ isEditMovieModalOpen: false });
|
this.setState({ isEditMovieModalOpen: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onInteractiveSearchPress = () => {
|
||||||
|
this.setState({ isInteractiveSearchModalOpen: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
onInteractiveSearchModalClose = () => {
|
||||||
|
this.setState({ isInteractiveSearchModalOpen: false });
|
||||||
|
};
|
||||||
|
|
||||||
onDeleteMoviePress = () => {
|
onDeleteMoviePress = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
isEditMovieModalOpen: false,
|
isEditMovieModalOpen: false,
|
||||||
@@ -265,7 +272,7 @@ class MovieDetails extends Component {
|
|||||||
ratings,
|
ratings,
|
||||||
path,
|
path,
|
||||||
sizeOnDisk,
|
sizeOnDisk,
|
||||||
qualityProfileId,
|
qualityProfileIds,
|
||||||
monitored,
|
monitored,
|
||||||
studio,
|
studio,
|
||||||
genres,
|
genres,
|
||||||
@@ -298,9 +305,9 @@ class MovieDetails extends Component {
|
|||||||
isEditMovieModalOpen,
|
isEditMovieModalOpen,
|
||||||
isDeleteMovieModalOpen,
|
isDeleteMovieModalOpen,
|
||||||
isInteractiveImportModalOpen,
|
isInteractiveImportModalOpen,
|
||||||
|
isInteractiveSearchModalOpen,
|
||||||
overviewHeight,
|
overviewHeight,
|
||||||
titleWidth,
|
titleWidth
|
||||||
selectedTabIndex
|
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const marqueeWidth = isSmallScreen ? titleWidth : (titleWidth - 150);
|
const marqueeWidth = isSmallScreen ? titleWidth : (titleWidth - 150);
|
||||||
@@ -326,6 +333,14 @@ class MovieDetails extends Component {
|
|||||||
onPress={onSearchPress}
|
onPress={onSearchPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<PageToolbarButton
|
||||||
|
label={translate('InteractiveSearch')}
|
||||||
|
iconName={icons.INTERACTIVE}
|
||||||
|
isSpinning={isSearching}
|
||||||
|
title={undefined}
|
||||||
|
onPress={this.onInteractiveSearchPress}
|
||||||
|
/>
|
||||||
|
|
||||||
<PageToolbarSeparator />
|
<PageToolbarSeparator />
|
||||||
|
|
||||||
<PageToolbarButton
|
<PageToolbarButton
|
||||||
@@ -557,7 +572,7 @@ class MovieDetails extends Component {
|
|||||||
<span className={styles.qualityProfileName}>
|
<span className={styles.qualityProfileName}>
|
||||||
{
|
{
|
||||||
<QualityProfileNameConnector
|
<QualityProfileNameConnector
|
||||||
qualityProfileId={qualityProfileId}
|
qualityProfileIds={qualityProfileIds}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
@@ -651,101 +666,39 @@ class MovieDetails extends Component {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<Tabs selectedIndex={this.state.tabIndex} onSelect={this.onTabSelect}>
|
<FieldSet legend={translate('History')}>
|
||||||
<TabList
|
<MovieHistoryTable
|
||||||
className={styles.tabList}
|
movieId={id}
|
||||||
>
|
/>
|
||||||
<Tab
|
</FieldSet>
|
||||||
className={styles.tab}
|
|
||||||
selectedClassName={styles.selectedTab}
|
|
||||||
>
|
|
||||||
{translate('History')}
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab
|
<FieldSet legend={translate('Files')}>
|
||||||
className={styles.tab}
|
<MovieFileEditorTable
|
||||||
selectedClassName={styles.selectedTab}
|
movieId={id}
|
||||||
>
|
/>
|
||||||
{translate('Search')}
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab
|
<ExtraFileTable
|
||||||
className={styles.tab}
|
movieId={id}
|
||||||
selectedClassName={styles.selectedTab}
|
/>
|
||||||
>
|
</FieldSet>
|
||||||
{translate('Files')}
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab
|
<FieldSet legend={translate('Cast')}>
|
||||||
className={styles.tab}
|
<MovieCastPostersConnector
|
||||||
selectedClassName={styles.selectedTab}
|
isSmallScreen={isSmallScreen}
|
||||||
>
|
/>
|
||||||
{translate('Titles')}
|
</FieldSet>
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab
|
<FieldSet legend={translate('Crew')}>
|
||||||
className={styles.tab}
|
<MovieCrewPostersConnector
|
||||||
selectedClassName={styles.selectedTab}
|
isSmallScreen={isSmallScreen}
|
||||||
>
|
/>
|
||||||
{translate('Cast')}
|
</FieldSet>
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab
|
|
||||||
className={styles.tab}
|
|
||||||
selectedClassName={styles.selectedTab}
|
|
||||||
>
|
|
||||||
{translate('Crew')}
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
{
|
|
||||||
selectedTabIndex === 1 &&
|
|
||||||
<div className={styles.filterIcon}>
|
|
||||||
<InteractiveSearchFilterMenuConnector />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
</TabList>
|
|
||||||
|
|
||||||
<TabPanel>
|
|
||||||
<MovieHistoryTable
|
|
||||||
movieId={id}
|
|
||||||
/>
|
|
||||||
</TabPanel>
|
|
||||||
|
|
||||||
<TabPanel>
|
|
||||||
<InteractiveSearchTable
|
|
||||||
movieId={id}
|
|
||||||
/>
|
|
||||||
</TabPanel>
|
|
||||||
|
|
||||||
<TabPanel>
|
|
||||||
<MovieFileEditorTable
|
|
||||||
movieId={id}
|
|
||||||
/>
|
|
||||||
<ExtraFileTable
|
|
||||||
movieId={id}
|
|
||||||
/>
|
|
||||||
</TabPanel>
|
|
||||||
|
|
||||||
<TabPanel>
|
|
||||||
<MovieTitlesTable
|
|
||||||
movieId={id}
|
|
||||||
/>
|
|
||||||
</TabPanel>
|
|
||||||
|
|
||||||
<TabPanel>
|
|
||||||
<MovieCastPostersConnector
|
|
||||||
isSmallScreen={isSmallScreen}
|
|
||||||
/>
|
|
||||||
</TabPanel>
|
|
||||||
|
|
||||||
<TabPanel>
|
|
||||||
<MovieCrewPostersConnector
|
|
||||||
isSmallScreen={isSmallScreen}
|
|
||||||
/>
|
|
||||||
</TabPanel>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
|
<FieldSet legend={translate('TitlesAndTranslations')}>
|
||||||
|
<MovieTitlesTable
|
||||||
|
movieId={id}
|
||||||
|
/>
|
||||||
|
</FieldSet>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<OrganizePreviewModalConnector
|
<OrganizePreviewModalConnector
|
||||||
@@ -777,6 +730,12 @@ class MovieDetails extends Component {
|
|||||||
showImportMode={false}
|
showImportMode={false}
|
||||||
onModalClose={this.onInteractiveImportModalClose}
|
onModalClose={this.onInteractiveImportModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<MovieInteractiveSearchModalConnector
|
||||||
|
isOpen={isInteractiveSearchModalOpen}
|
||||||
|
movieId={id}
|
||||||
|
onModalClose={this.onInteractiveSearchModalClose}
|
||||||
|
/>
|
||||||
</PageContentBody>
|
</PageContentBody>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
);
|
);
|
||||||
@@ -795,7 +754,7 @@ MovieDetails.propTypes = {
|
|||||||
ratings: PropTypes.object.isRequired,
|
ratings: PropTypes.object.isRequired,
|
||||||
path: PropTypes.string.isRequired,
|
path: PropTypes.string.isRequired,
|
||||||
sizeOnDisk: PropTypes.number.isRequired,
|
sizeOnDisk: PropTypes.number.isRequired,
|
||||||
qualityProfileId: PropTypes.number.isRequired,
|
qualityProfileIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
monitored: PropTypes.bool.isRequired,
|
monitored: PropTypes.bool.isRequired,
|
||||||
status: PropTypes.string.isRequired,
|
status: PropTypes.string.isRequired,
|
||||||
studio: PropTypes.string,
|
studio: PropTypes.string,
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class MovieTitlesRow extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MovieTitlesRow.propTypes = {
|
MovieTitlesRow.propTypes = {
|
||||||
id: PropTypes.number.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
language: PropTypes.object.isRequired,
|
language: PropTypes.object.isRequired,
|
||||||
sourceType: PropTypes.string.isRequired
|
sourceType: PropTypes.string.isRequired
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
.container {
|
||||||
|
border: 1px solid var(--borderColor);
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--inputBackgroundColor);
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MovieTitlesTableContentConnector from './MovieTitlesTableContentConnector';
|
import MovieTitlesTableContentConnector from './MovieTitlesTableContentConnector';
|
||||||
|
import styles from './MovieTitlesTable.css';
|
||||||
|
|
||||||
function MovieTitlesTable(props) {
|
function MovieTitlesTable(props) {
|
||||||
const {
|
const {
|
||||||
@@ -7,9 +8,11 @@ function MovieTitlesTable(props) {
|
|||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MovieTitlesTableContentConnector
|
<div className={styles.container}>
|
||||||
{...otherProps}
|
<MovieTitlesTableContentConnector
|
||||||
/>
|
{...otherProps}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
|
||||||
import Table from 'Components/Table/Table';
|
import Table from 'Components/Table/Table';
|
||||||
import TableBody from 'Components/Table/TableBody';
|
import TableBody from 'Components/Table/TableBody';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
@@ -10,7 +9,7 @@ import styles from './MovieTitlesTableContent.css';
|
|||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
name: 'altTitle',
|
name: 'altTitle',
|
||||||
label: translate('AlternativeTitle'),
|
label: translate('Title'),
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -32,40 +31,25 @@ class MovieTitlesTableContent extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
isFetching,
|
titles
|
||||||
isPopulated,
|
|
||||||
error,
|
|
||||||
items
|
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const hasItems = !!items.length;
|
const hasItems = !!titles.length;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{
|
{
|
||||||
isFetching &&
|
!hasItems &&
|
||||||
<LoadingIndicator />
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
!isFetching && !!error &&
|
|
||||||
<div className={styles.blankpad}>
|
|
||||||
{translate('UnableToLoadAltTitle')}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
isPopulated && !hasItems && !error &&
|
|
||||||
<div className={styles.blankpad}>
|
<div className={styles.blankpad}>
|
||||||
{translate('NoAltTitle')}
|
{translate('NoAltTitle')}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
isPopulated && hasItems && !error &&
|
hasItems &&
|
||||||
<Table columns={columns}>
|
<Table columns={columns}>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{
|
{
|
||||||
items.reverse().map((item) => {
|
titles.reverse().map((item) => {
|
||||||
return (
|
return (
|
||||||
<MovieTitlesRow
|
<MovieTitlesRow
|
||||||
key={item.id}
|
key={item.id}
|
||||||
@@ -83,10 +67,7 @@ class MovieTitlesTableContent extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MovieTitlesTableContent.propTypes = {
|
MovieTitlesTableContent.propTypes = {
|
||||||
isFetching: PropTypes.bool.isRequired,
|
titles: PropTypes.arrayOf(PropTypes.object).isRequired
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
|
||||||
error: PropTypes.object,
|
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MovieTitlesTableContent;
|
export default MovieTitlesTableContent;
|
||||||
|
|||||||
@@ -2,13 +2,40 @@ import PropTypes from 'prop-types';
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
import createMovieSelector from 'Store/Selectors/createMovieSelector';
|
||||||
import MovieTitlesTableContent from './MovieTitlesTableContent';
|
import MovieTitlesTableContent from './MovieTitlesTableContent';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.movies,
|
createMovieSelector(),
|
||||||
(movies) => {
|
(movie) => {
|
||||||
return movies;
|
let titles = [];
|
||||||
|
|
||||||
|
if (movie.alternateTitles) {
|
||||||
|
titles = movie.alternateTitles.map((title) => {
|
||||||
|
return {
|
||||||
|
id: `title_${title.id}`,
|
||||||
|
title: title.title,
|
||||||
|
language: title.language || 'Unknown',
|
||||||
|
sourceType: 'Alternative Title'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (movie.translations) {
|
||||||
|
titles = titles.concat(movie.translations.map((title) => {
|
||||||
|
return {
|
||||||
|
id: `translation_${title.id}`,
|
||||||
|
title: title.title,
|
||||||
|
language: title.language || 'Unknown',
|
||||||
|
sourceType: 'Translation'
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
titles
|
||||||
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -23,14 +50,14 @@ class MovieTitlesTableContentConnector extends Component {
|
|||||||
// Render
|
// Render
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const movie = this.props.items.filter((obj) => {
|
const {
|
||||||
return obj.id === this.props.movieId;
|
titles
|
||||||
});
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MovieTitlesTableContent
|
<MovieTitlesTableContent
|
||||||
{...this.props}
|
{...this.props}
|
||||||
items={movie[0].alternateTitles}
|
titles={titles}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -38,7 +65,7 @@ class MovieTitlesTableContentConnector extends Component {
|
|||||||
|
|
||||||
MovieTitlesTableContentConnector.propTypes = {
|
MovieTitlesTableContentConnector.propTypes = {
|
||||||
movieId: PropTypes.number.isRequired,
|
movieId: PropTypes.number.isRequired,
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired
|
titles: PropTypes.arrayOf(PropTypes.object).isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps)(MovieTitlesTableContentConnector);
|
export default connect(createMapStateToProps, mapDispatchToProps)(MovieTitlesTableContentConnector);
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class EditMovieModalContent extends Component {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
monitored,
|
monitored,
|
||||||
qualityProfileId,
|
qualityProfileIds,
|
||||||
minimumAvailability,
|
minimumAvailability,
|
||||||
// Id,
|
// Id,
|
||||||
path,
|
path,
|
||||||
@@ -110,12 +110,12 @@ class EditMovieModalContent 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>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ function createMapStateToProps() {
|
|||||||
|
|
||||||
const movieSettings = {
|
const movieSettings = {
|
||||||
monitored: movie.monitored,
|
monitored: movie.monitored,
|
||||||
qualityProfileId: movie.qualityProfileId,
|
qualityProfileIds: movie.qualityProfileIds,
|
||||||
minimumAvailability: movie.minimumAvailability,
|
minimumAvailability: movie.minimumAvailability,
|
||||||
path: movie.path,
|
path: movie.path,
|
||||||
tags: movie.tags
|
tags: movie.tags
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import AvailabilitySelectInput from 'Components/Form/AvailabilitySelectInput';
|
import AvailabilitySelectInput from 'Components/Form/AvailabilitySelectInput';
|
||||||
import QualityProfileSelectInputConnector from 'Components/Form/QualityProfileSelectInputConnector';
|
|
||||||
import RootFolderSelectInputConnector from 'Components/Form/RootFolderSelectInputConnector';
|
import RootFolderSelectInputConnector from 'Components/Form/RootFolderSelectInputConnector';
|
||||||
import SelectInput from 'Components/Form/SelectInput';
|
import SelectInput from 'Components/Form/SelectInput';
|
||||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||||
@@ -11,6 +10,7 @@ import MoveMovieModal from 'Movie/MoveMovie/MoveMovieModal';
|
|||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import DeleteMovieModal from './Delete/DeleteMovieModal';
|
import DeleteMovieModal from './Delete/DeleteMovieModal';
|
||||||
import MovieEditorFooterLabel from './MovieEditorFooterLabel';
|
import MovieEditorFooterLabel from './MovieEditorFooterLabel';
|
||||||
|
import QualityProfilesModal from './QualityProfiles/QualityProfilesModal';
|
||||||
import TagsModal from './Tags/TagsModal';
|
import TagsModal from './Tags/TagsModal';
|
||||||
import styles from './MovieEditorFooter.css';
|
import styles from './MovieEditorFooter.css';
|
||||||
|
|
||||||
@@ -26,12 +26,13 @@ class MovieEditorFooter extends Component {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
monitored: NO_CHANGE,
|
monitored: NO_CHANGE,
|
||||||
qualityProfileId: NO_CHANGE,
|
|
||||||
minimumAvailability: NO_CHANGE,
|
minimumAvailability: NO_CHANGE,
|
||||||
rootFolderPath: NO_CHANGE,
|
rootFolderPath: NO_CHANGE,
|
||||||
savingTags: false,
|
savingTags: false,
|
||||||
|
savingQualityProfiles: false,
|
||||||
isDeleteMovieModalOpen: false,
|
isDeleteMovieModalOpen: false,
|
||||||
isTagsModalOpen: false,
|
isTagsModalOpen: false,
|
||||||
|
isQualityProfilesModalOpen: false,
|
||||||
isConfirmMoveModalOpen: false,
|
isConfirmMoveModalOpen: false,
|
||||||
destinationRootFolder: null
|
destinationRootFolder: null
|
||||||
};
|
};
|
||||||
@@ -46,10 +47,10 @@ class MovieEditorFooter extends Component {
|
|||||||
if (prevProps.isSaving && !isSaving && !saveError) {
|
if (prevProps.isSaving && !isSaving && !saveError) {
|
||||||
this.setState({
|
this.setState({
|
||||||
monitored: NO_CHANGE,
|
monitored: NO_CHANGE,
|
||||||
qualityProfileId: NO_CHANGE,
|
|
||||||
minimumAvailability: NO_CHANGE,
|
minimumAvailability: NO_CHANGE,
|
||||||
rootFolderPath: NO_CHANGE,
|
rootFolderPath: NO_CHANGE,
|
||||||
savingTags: false
|
savingTags: false,
|
||||||
|
savingQualityProfiles: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,6 +92,17 @@ class MovieEditorFooter extends Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onApplyQualityProfilesPress = (qualityProfileIds) => {
|
||||||
|
this.setState({
|
||||||
|
savingQualityProfiles: true,
|
||||||
|
isQualityProfilesModalOpen: false
|
||||||
|
});
|
||||||
|
|
||||||
|
this.props.onSaveSelected({
|
||||||
|
qualityProfileIds
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
onDeleteSelectedPress = () => {
|
onDeleteSelectedPress = () => {
|
||||||
this.setState({ isDeleteMovieModalOpen: true });
|
this.setState({ isDeleteMovieModalOpen: true });
|
||||||
};
|
};
|
||||||
@@ -107,6 +119,14 @@ class MovieEditorFooter extends Component {
|
|||||||
this.setState({ isTagsModalOpen: false });
|
this.setState({ isTagsModalOpen: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onQualityProfilesPress = () => {
|
||||||
|
this.setState({ isQualityProfilesModalOpen: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
onQualityProfilesModalClose = () => {
|
||||||
|
this.setState({ isQualityProfilesModalOpen: false });
|
||||||
|
};
|
||||||
|
|
||||||
onSaveRootFolderPress = () => {
|
onSaveRootFolderPress = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
isConfirmMoveModalOpen: false,
|
isConfirmMoveModalOpen: false,
|
||||||
@@ -143,11 +163,13 @@ class MovieEditorFooter extends Component {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
monitored,
|
monitored,
|
||||||
qualityProfileId,
|
qualityProfileIds,
|
||||||
minimumAvailability,
|
minimumAvailability,
|
||||||
rootFolderPath,
|
rootFolderPath,
|
||||||
savingTags,
|
savingTags,
|
||||||
|
savingQualityProfiles,
|
||||||
isTagsModalOpen,
|
isTagsModalOpen,
|
||||||
|
isQualityProfilesModalOpen,
|
||||||
isDeleteMovieModalOpen,
|
isDeleteMovieModalOpen,
|
||||||
isConfirmMoveModalOpen,
|
isConfirmMoveModalOpen,
|
||||||
destinationRootFolder
|
destinationRootFolder
|
||||||
@@ -178,17 +200,18 @@ class MovieEditorFooter extends Component {
|
|||||||
|
|
||||||
<div className={styles.inputContainer}>
|
<div className={styles.inputContainer}>
|
||||||
<MovieEditorFooterLabel
|
<MovieEditorFooterLabel
|
||||||
label={translate('QualityProfile')}
|
label={translate('QualityProfiles')}
|
||||||
isSaving={isSaving && qualityProfileId !== NO_CHANGE}
|
isSaving={isSaving && qualityProfileIds !== NO_CHANGE}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<QualityProfileSelectInputConnector
|
<SpinnerButton
|
||||||
name="qualityProfileId"
|
className={styles.tagsButton}
|
||||||
value={qualityProfileId}
|
isSpinning={isSaving && savingTags && savingQualityProfiles}
|
||||||
includeNoChange={true}
|
isDisabled={!selectedCount || isOrganizingMovie}
|
||||||
isDisabled={!selectedCount}
|
onPress={this.onQualityProfilesPress}
|
||||||
onChange={this.onInputChange}
|
>
|
||||||
/>
|
{translate('SetQualityProfiles')}
|
||||||
|
</SpinnerButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.inputContainer}>
|
<div className={styles.inputContainer}>
|
||||||
@@ -243,7 +266,7 @@ class MovieEditorFooter extends Component {
|
|||||||
|
|
||||||
<SpinnerButton
|
<SpinnerButton
|
||||||
className={styles.tagsButton}
|
className={styles.tagsButton}
|
||||||
isSpinning={isSaving && savingTags}
|
isSpinning={isSaving && savingTags && savingQualityProfiles}
|
||||||
isDisabled={!selectedCount || isOrganizingMovie}
|
isDisabled={!selectedCount || isOrganizingMovie}
|
||||||
onPress={this.onTagsPress}
|
onPress={this.onTagsPress}
|
||||||
>
|
>
|
||||||
@@ -271,6 +294,13 @@ class MovieEditorFooter extends Component {
|
|||||||
onModalClose={this.onTagsModalClose}
|
onModalClose={this.onTagsModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<QualityProfilesModal
|
||||||
|
isOpen={isQualityProfilesModalOpen}
|
||||||
|
movieIds={movieIds}
|
||||||
|
onApplyQualityProfilesPress={this.onApplyQualityProfilesPress}
|
||||||
|
onModalClose={this.onQualityProfilesModalClose}
|
||||||
|
/>
|
||||||
|
|
||||||
<DeleteMovieModal
|
<DeleteMovieModal
|
||||||
isOpen={isDeleteMovieModalOpen}
|
isOpen={isDeleteMovieModalOpen}
|
||||||
movieIds={movieIds}
|
movieIds={movieIds}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import Modal from 'Components/Modal/Modal';
|
||||||
|
import QualityProfilesModalContent from './QualityProfilesModalContent';
|
||||||
|
|
||||||
|
function QualityProfilesModal(props) {
|
||||||
|
const {
|
||||||
|
isOpen,
|
||||||
|
onModalClose,
|
||||||
|
...otherProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
>
|
||||||
|
<QualityProfilesModalContent
|
||||||
|
{...otherProps}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
QualityProfilesModal.propTypes = {
|
||||||
|
isOpen: PropTypes.bool.isRequired,
|
||||||
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default QualityProfilesModal;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
.renameIcon {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result {
|
||||||
|
padding-top: 4px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import Form from 'Components/Form/Form';
|
||||||
|
import FormGroup from 'Components/Form/FormGroup';
|
||||||
|
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||||
|
import FormLabel from 'Components/Form/FormLabel';
|
||||||
|
import Button from 'Components/Link/Button';
|
||||||
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
|
import { inputTypes, kinds } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
|
||||||
|
class QualityProfilesModalContent extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
qualityProfileIds: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
onInputChange = ({ name, value }) => {
|
||||||
|
this.setState({ [name]: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
onApplyQualityProfilesPress = () => {
|
||||||
|
const {
|
||||||
|
qualityProfileIds
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
this.props.onApplyQualityProfilesPress(qualityProfileIds);
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
onModalClose
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
qualityProfileIds
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContent onModalClose={onModalClose}>
|
||||||
|
<ModalHeader>
|
||||||
|
{translate('QualityProfiles')}
|
||||||
|
</ModalHeader>
|
||||||
|
|
||||||
|
<ModalBody>
|
||||||
|
<Form>
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('QualityProfiles')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.QUALITY_PROFILE_SELECT}
|
||||||
|
name="qualityProfileIds"
|
||||||
|
value={qualityProfileIds}
|
||||||
|
onChange={this.onInputChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</Form>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button onPress={onModalClose}>
|
||||||
|
{translate('Cancel')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
kind={kinds.PRIMARY}
|
||||||
|
onPress={this.onApplyQualityProfilesPress}
|
||||||
|
>
|
||||||
|
{translate('Apply')}
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QualityProfilesModalContent.propTypes = {
|
||||||
|
onModalClose: PropTypes.func.isRequired,
|
||||||
|
onApplyQualityProfilesPress: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default QualityProfilesModalContent;
|
||||||
@@ -62,6 +62,7 @@ class MovieHistoryRow extends Component {
|
|||||||
sourceTitle,
|
sourceTitle,
|
||||||
quality,
|
quality,
|
||||||
customFormats,
|
customFormats,
|
||||||
|
customFormatScore,
|
||||||
languages,
|
languages,
|
||||||
qualityCutoffNotMet,
|
qualityCutoffNotMet,
|
||||||
date,
|
date,
|
||||||
@@ -106,7 +107,7 @@ class MovieHistoryRow extends Component {
|
|||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
<TableRowCell key={name}>
|
<TableRowCell key={name}>
|
||||||
{formatCustomFormatScore(data.customFormatScore)}
|
{formatCustomFormatScore(customFormatScore)}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|
||||||
<RelativeDateCellConnector
|
<RelativeDateCellConnector
|
||||||
@@ -161,7 +162,8 @@ MovieHistoryRow.propTypes = {
|
|||||||
sourceTitle: PropTypes.string.isRequired,
|
sourceTitle: PropTypes.string.isRequired,
|
||||||
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
|
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
quality: PropTypes.object.isRequired,
|
quality: PropTypes.object.isRequired,
|
||||||
customFormats: PropTypes.arrayOf(PropTypes.object).isRequired,
|
customFormats: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
customFormatScore: PropTypes.number.isRequired,
|
||||||
qualityCutoffNotMet: PropTypes.bool.isRequired,
|
qualityCutoffNotMet: PropTypes.bool.isRequired,
|
||||||
date: PropTypes.string.isRequired,
|
date: PropTypes.string.isRequired,
|
||||||
data: PropTypes.object.isRequired,
|
data: PropTypes.object.isRequired,
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
.container {
|
||||||
|
border: 1px solid var(--borderColor);
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--inputBackgroundColor);
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MovieHistoryTableContentConnector from './MovieHistoryTableContentConnector';
|
import MovieHistoryTableContentConnector from './MovieHistoryTableContentConnector';
|
||||||
|
import styles from './MovieHistoryTable.css';
|
||||||
|
|
||||||
function MovieHistoryTable(props) {
|
function MovieHistoryTable(props) {
|
||||||
const {
|
const {
|
||||||
@@ -7,9 +8,11 @@ function MovieHistoryTable(props) {
|
|||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MovieHistoryTableContentConnector
|
<div className={styles.container}>
|
||||||
{...otherProps}
|
<MovieHistoryTableContentConnector
|
||||||
/>
|
{...otherProps}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,16 +21,20 @@ class MovieIndexFooter extends PureComponent {
|
|||||||
let totalFileSize = 0;
|
let totalFileSize = 0;
|
||||||
|
|
||||||
movies.forEach((s) => {
|
movies.forEach((s) => {
|
||||||
|
const { statistics = {} } = s;
|
||||||
|
|
||||||
if (s.hasFile) {
|
const {
|
||||||
movieFiles += 1;
|
movieFileCount = 0,
|
||||||
}
|
sizeOnDisk = 0
|
||||||
|
} = statistics;
|
||||||
|
|
||||||
|
movieFiles += movieFileCount;
|
||||||
|
|
||||||
if (s.monitored) {
|
if (s.monitored) {
|
||||||
monitored++;
|
monitored++;
|
||||||
}
|
}
|
||||||
|
|
||||||
totalFileSize += s.sizeOnDisk;
|
totalFileSize += sizeOnDisk;
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -14,16 +14,14 @@ function createUnoptimizedSelector() {
|
|||||||
monitored,
|
monitored,
|
||||||
status,
|
status,
|
||||||
statistics,
|
statistics,
|
||||||
sizeOnDisk,
|
sizeOnDisk
|
||||||
hasFile
|
|
||||||
} = s;
|
} = s;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
monitored,
|
monitored,
|
||||||
status,
|
status,
|
||||||
statistics,
|
statistics,
|
||||||
sizeOnDisk,
|
sizeOnDisk
|
||||||
hasFile
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { createSelector } from 'reselect';
|
|||||||
import * as commandNames from 'Commands/commandNames';
|
import * as commandNames from 'Commands/commandNames';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
import createExecutingCommandsSelector from 'Store/Selectors/createExecutingCommandsSelector';
|
import createExecutingCommandsSelector from 'Store/Selectors/createExecutingCommandsSelector';
|
||||||
import createMovieQualityProfileSelector from 'Store/Selectors/createMovieQualityProfileSelector';
|
|
||||||
import createMovieSelector from 'Store/Selectors/createMovieSelector';
|
import createMovieSelector from 'Store/Selectors/createMovieSelector';
|
||||||
|
|
||||||
function selectShowSearchAction() {
|
function selectShowSearchAction() {
|
||||||
@@ -29,13 +28,11 @@ function selectShowSearchAction() {
|
|||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createMovieSelector(),
|
createMovieSelector(),
|
||||||
createMovieQualityProfileSelector(),
|
|
||||||
selectShowSearchAction(),
|
selectShowSearchAction(),
|
||||||
createExecutingCommandsSelector(),
|
createExecutingCommandsSelector(),
|
||||||
(state) => state.queue.details.items,
|
(state) => state.queue.details.items,
|
||||||
(
|
(
|
||||||
movie,
|
movie,
|
||||||
qualityProfile,
|
|
||||||
showSearchAction,
|
showSearchAction,
|
||||||
executingCommands,
|
executingCommands,
|
||||||
queueItems
|
queueItems
|
||||||
@@ -68,7 +65,6 @@ function createMapStateToProps() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...movie,
|
...movie,
|
||||||
qualityProfile,
|
|
||||||
showSearchAction,
|
showSearchAction,
|
||||||
isRefreshingMovie,
|
isRefreshingMovie,
|
||||||
isSearchingMovie,
|
isSearchingMovie,
|
||||||
|
|||||||
@@ -91,14 +91,14 @@ class MovieIndexOverview extends Component {
|
|||||||
title,
|
title,
|
||||||
overview,
|
overview,
|
||||||
monitored,
|
monitored,
|
||||||
hasFile,
|
|
||||||
isAvailable,
|
isAvailable,
|
||||||
status,
|
status,
|
||||||
titleSlug,
|
titleSlug,
|
||||||
|
statistics,
|
||||||
images,
|
images,
|
||||||
posterWidth,
|
posterWidth,
|
||||||
posterHeight,
|
posterHeight,
|
||||||
qualityProfile,
|
qualityProfileIds,
|
||||||
overviewOptions,
|
overviewOptions,
|
||||||
showSearchAction,
|
showSearchAction,
|
||||||
showRelativeDates,
|
showRelativeDates,
|
||||||
@@ -119,6 +119,11 @@ class MovieIndexOverview extends Component {
|
|||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
sizeOnDisk,
|
||||||
|
movieFileCount
|
||||||
|
} = statistics;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isEditMovieModalOpen,
|
isEditMovieModalOpen,
|
||||||
isDeleteMovieModalOpen
|
isDeleteMovieModalOpen
|
||||||
@@ -169,7 +174,7 @@ class MovieIndexOverview extends Component {
|
|||||||
|
|
||||||
<MovieIndexProgressBar
|
<MovieIndexProgressBar
|
||||||
monitored={monitored}
|
monitored={monitored}
|
||||||
hasFile={hasFile}
|
hasFile={movieFileCount > 0}
|
||||||
isAvailable={isAvailable}
|
isAvailable={isAvailable}
|
||||||
status={status}
|
status={status}
|
||||||
posterWidth={posterWidth}
|
posterWidth={posterWidth}
|
||||||
@@ -248,11 +253,12 @@ class MovieIndexOverview extends Component {
|
|||||||
<MovieIndexOverviewInfo
|
<MovieIndexOverviewInfo
|
||||||
height={overviewHeight}
|
height={overviewHeight}
|
||||||
monitored={monitored}
|
monitored={monitored}
|
||||||
qualityProfile={qualityProfile}
|
qualityProfileIds={qualityProfileIds}
|
||||||
showRelativeDates={showRelativeDates}
|
showRelativeDates={showRelativeDates}
|
||||||
shortDateFormat={shortDateFormat}
|
shortDateFormat={shortDateFormat}
|
||||||
longDateFormat={longDateFormat}
|
longDateFormat={longDateFormat}
|
||||||
timeFormat={timeFormat}
|
timeFormat={timeFormat}
|
||||||
|
sizeOnDisk={sizeOnDisk}
|
||||||
{...overviewOptions}
|
{...overviewOptions}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
@@ -282,15 +288,15 @@ MovieIndexOverview.propTypes = {
|
|||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
overview: PropTypes.string.isRequired,
|
overview: PropTypes.string.isRequired,
|
||||||
monitored: PropTypes.bool.isRequired,
|
monitored: PropTypes.bool.isRequired,
|
||||||
hasFile: PropTypes.bool.isRequired,
|
|
||||||
isAvailable: PropTypes.bool.isRequired,
|
isAvailable: PropTypes.bool.isRequired,
|
||||||
status: PropTypes.string.isRequired,
|
status: PropTypes.string.isRequired,
|
||||||
titleSlug: PropTypes.string.isRequired,
|
titleSlug: PropTypes.string.isRequired,
|
||||||
|
statistics: PropTypes.object.isRequired,
|
||||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
posterWidth: PropTypes.number.isRequired,
|
posterWidth: PropTypes.number.isRequired,
|
||||||
posterHeight: PropTypes.number.isRequired,
|
posterHeight: PropTypes.number.isRequired,
|
||||||
rowHeight: PropTypes.number.isRequired,
|
rowHeight: PropTypes.number.isRequired,
|
||||||
qualityProfile: PropTypes.object.isRequired,
|
qualityProfileIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
overviewOptions: PropTypes.object.isRequired,
|
overviewOptions: PropTypes.object.isRequired,
|
||||||
showSearchAction: PropTypes.bool.isRequired,
|
showSearchAction: PropTypes.bool.isRequired,
|
||||||
showRelativeDates: PropTypes.bool.isRequired,
|
showRelativeDates: PropTypes.bool.isRequired,
|
||||||
@@ -312,4 +318,11 @@ MovieIndexOverview.propTypes = {
|
|||||||
queueState: PropTypes.string
|
queueState: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MovieIndexOverview.defaultProps = {
|
||||||
|
statistics: {
|
||||||
|
movieFileCount: 0,
|
||||||
|
sizeOnDisk: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default MovieIndexOverview;
|
export default MovieIndexOverview;
|
||||||
|
|||||||
@@ -79,13 +79,13 @@ function getInfoRowProps(row, props) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name === 'qualityProfileId') {
|
// if (name === 'qualityProfileId') {
|
||||||
return {
|
// return {
|
||||||
title: 'Quality Profile',
|
// title: 'Quality Profile',
|
||||||
iconName: icons.PROFILE,
|
// iconName: icons.PROFILE,
|
||||||
label: props.qualityProfile.name
|
// label: props.qualityProfile.name
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (name === 'added') {
|
if (name === 'added') {
|
||||||
const {
|
const {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user