1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-09 15:01:39 -04:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Bogdan
9ed2553883 Fixed: (RadarrImport) Treat redirects as HTTP errors 2023-06-03 21:20:31 +03:00
1118 changed files with 12156 additions and 25320 deletions

View File

@@ -270,7 +270,7 @@ dotnet_diagnostic.CA5397.severity = suggestion
dotnet_diagnostic.SYSLIB0006.severity = none
[*.{js,html,hbs,less,css,ts,tsx}]
[*.{js,html,js,hbs,less,css}]
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

View File

@@ -1,5 +1,5 @@
name: Bug Report
description: 'Report a new bug, if you are not 100% certain this is a bug please go to our Discord first'
description: 'Report a new bug, if you are not 100% certain this is a bug please go to our Reddit or Discord first'
labels: ['Type: Bug', 'Status: Needs Triage']
body:
- type: checkboxes
@@ -65,18 +65,18 @@ body:
required: true
- type: textarea
attributes:
label: Trace Logs? **Not Optional**
label: Trace Logs?
description: |
Trace Logs (https://wiki.servarr.com/radarr/troubleshooting#logging-and-log-files)
***Generally speaking, all bug reports MUST have trace logs provided.***
***Generally speaking, all bug reports must have trace logs provided.***
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
Additionally, any additional info? Screenshots? References? Anything that will give us more context about the issue you are encountering!
validations:
required: true
- type: checkboxes
attributes:
label: Trace Logs have been provided as applicable. Reports will be closed if the required logs are not provided.
description: Trace logs are **generally required** and are not optional for all bug reports and contain `trace`. Info logs are invalid for bug reports and do not contain `debug` nor `trace`
label: Trace Logs have been provided as applicable. Reports may be closed if the required logs are not provided.
description: Trace logs are generally required for all bug reports
options:
- label: I have read and followed the steps in the wiki link above and provided the required trace logs - the logs contain `trace` - that are relevant and show this issue.
- label: I have followed the steps in the wiki link above and provided the required trace logs that are relevant and show this issue.
required: true

View File

@@ -3,3 +3,6 @@ contact_links:
- name: Support via Discord
url: https://radarr.video/discord
about: Chat with users and devs on support and setup related topics.
- name: Support via Reddit
url: https://reddit.com/r/radarr
about: Discuss and search thru support topics.

View File

@@ -1,16 +0,0 @@
# Configuration for Label Actions - https://github.com/dessant/label-actions
'Type: Support':
comment: >
:wave: @{issue-author}, we use the issue tracker exclusively
for bug reports and feature requests. However, this issue appears
to be a support request. Please hop over onto our [Discord](https://radarr.video/discord).
close: true
close-reason: 'not planned'
'Status: Logs Needed':
comment: >
:wave: @{issue-author}, In order to help you further we'll need to see logs.
You'll need to enable trace logging and replicate the problem that you encountered.
Guidance on how to enable trace logging can be found in
our [troubleshooting guide](https://wiki.servarr.com/radarr/troubleshooting#logging-and-log-files).

View File

@@ -1,17 +0,0 @@
name: 'Label Actions'
on:
issues:
types: [labeled, unlabeled]
permissions:
contents: read
issues: write
jobs:
action:
runs-on: ubuntu-latest
steps:
- uses: dessant/label-actions@v3
with:
process-only: 'issues'

36
.github/workflows/support.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: 'Support requests'
on:
issues:
types: [labeled, unlabeled, reopened]
permissions: {}
jobs:
support:
permissions:
issues: write # to modify issues
runs-on: ubuntu-latest
steps:
- uses: dessant/support-requests@v3
with:
github-token: ${{ github.token }}
support-label: 'Type: Support'
issue-comment: >
:wave: @{issue-author}, we use the issue tracker exclusively
for bug reports and feature requests. However, this issue appears
to be a support request. Please hop over onto our [Discord](https://radarr.video/discord)
or [Subreddit](https://reddit.com/r/radarr)
close-issue: true
lock-issue: false
- uses: dessant/support-requests@v3
with:
github-token: ${{ github.token }}
support-label: 'Status: Logs Needed'
issue-comment: >
:wave: @{issue-author}, In order to help you further we'll need to see logs.
You'll need to enable trace logging and replicate the problem that you encountered.
Guidance on how to enable trace logging can be found in
our [troubleshooting guide](https://wiki.servarr.com/radarr/troubleshooting#logging-and-log-files).
close-issue: false
lock-issue: false

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 331 B

After

Width:  |  Height:  |  Size: 577 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 666 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 987 B

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -35,6 +35,7 @@ Note that only one type of a given movie is supported. If you want both an 4k ve
[![Wiki](https://img.shields.io/badge/servarr-wiki-181717.svg?maxAge=60)](https://wiki.servarr.com/radarr)
[![Discord](https://img.shields.io/badge/discord-chat-7289DA.svg?maxAge=60)](https://radarr.video/discord)
[![Reddit](https://img.shields.io/badge/reddit-discussion-FF4500.svg?maxAge=60)](https://www.reddit.com/r/Radarr)
Note: GitHub Issues are for Bugs and Feature Requests Only

View File

@@ -9,13 +9,13 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '5.0.3'
majorVersion: '4.6.0'
minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.413'
dotnetVersion: '6.0.408'
nodeVersion: '16.X'
innoVersion: '6.2.0'
windowsImage: 'windows-2022'
@@ -27,10 +27,6 @@ trigger:
include:
- develop
- master
paths:
exclude:
- .github
- src/Radarr.Api.*/openapi.json
pr:
branches:
@@ -38,7 +34,6 @@ pr:
- develop
paths:
exclude:
- .github
- src/NzbDrone.Core/Localization/Core
- src/Radarr.Api.*/openapi.json
@@ -368,7 +363,7 @@ stages:
- bash: |
echo "Uploading source maps to sentry"
curl -sL https://sentry.io/get-cli/ | bash
RELEASENAME="Radarr@${RADARRVERSION}-${BUILD_SOURCEBRANCHNAME}"
RELEASENAME="${RADARRVERSION}-${BUILD_SOURCEBRANCHNAME}"
sentry-cli releases new --finalize -p radarr -p radarr-ui -p radarr-update "${RELEASENAME}"
sentry-cli releases -p radarr-ui files "${RELEASENAME}" upload-sourcemaps _output/UI/ --rewrite
sentry-cli releases set-commits --auto "${RELEASENAME}"
@@ -541,8 +536,8 @@ stages:
testRunTitle: '$(testName) Unit Tests'
failTaskOnFailedTests: true
- job: Unit_LinuxCore_Postgres14
displayName: Unit Native LinuxCore with Postgres14 Database
- job: Unit_LinuxCore_Postgres
displayName: Unit Native LinuxCore with Postgres Database
dependsOn: Prepare
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
variables:
@@ -594,63 +589,7 @@ stages:
inputs:
testResultsFormat: 'NUnit'
testResultsFiles: '**/TestResult.xml'
testRunTitle: 'LinuxCore Postgres14 Unit Tests'
failTaskOnFailedTests: true
- job: Unit_LinuxCore_Postgres15
displayName: Unit Native LinuxCore with Postgres15 Database
dependsOn: Prepare
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
variables:
pattern: 'Radarr.*.linux-core-x64.tar.gz'
artifactName: linux-x64-tests
Radarr__Postgres__Host: 'localhost'
Radarr__Postgres__Port: '5432'
Radarr__Postgres__User: 'radarr'
Radarr__Postgres__Password: 'radarr'
pool:
vmImage: ${{ variables.linuxImage }}
timeoutInMinutes: 10
steps:
- task: UseDotNet@2
displayName: 'Install .net core'
inputs:
version: $(dotnetVersion)
- checkout: none
- task: DownloadPipelineArtifact@2
displayName: Download Test Artifact
inputs:
buildType: 'current'
artifactName: $(artifactName)
targetPath: $(testsFolder)
- bash: |
chmod a+x _tests/ffprobe
displayName: Make ffprobe Executable
- bash: find ${TESTSFOLDER} -name "Radarr.Test.Dummy" -exec chmod a+x {} \;
displayName: Make Test Dummy Executable
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
- bash: |
docker run -d --name=postgres15 \
-e POSTGRES_PASSWORD=radarr \
-e POSTGRES_USER=radarr \
-p 5432:5432/tcp \
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
postgres:15
displayName: Start postgres
- bash: |
chmod a+x ${TESTSFOLDER}/test.sh
ls -lR ${TESTSFOLDER}
${TESTSFOLDER}/test.sh Linux Unit Test
displayName: Run Tests
- task: PublishTestResults@2
displayName: Publish Test Results
inputs:
testResultsFormat: 'NUnit'
testResultsFiles: '**/TestResult.xml'
testRunTitle: 'LinuxCore Postgres15 Unit Tests'
testRunTitle: 'LinuxCore Postgres Unit Tests'
failTaskOnFailedTests: true
- stage: Integration
@@ -736,8 +675,8 @@ stages:
failTaskOnFailedTests: true
displayName: Publish Test Results
- job: Integration_LinuxCore_Postgres14
displayName: Integration Native LinuxCore with Postgres14 Database
- job: Integration_LinuxCore_Postgres
displayName: Integration Native LinuxCore with Postgres Database
dependsOn: Prepare
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
variables:
@@ -794,70 +733,7 @@ stages:
inputs:
testResultsFormat: 'NUnit'
testResultsFiles: '**/TestResult.xml'
testRunTitle: 'Integration LinuxCore Postgres14 Database Integration Tests'
failTaskOnFailedTests: true
displayName: Publish Test Results
- job: Integration_LinuxCore_Postgres15
displayName: Integration Native LinuxCore with Postgres Database
dependsOn: Prepare
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
variables:
pattern: 'Radarr.*.linux-core-x64.tar.gz'
Radarr__Postgres__Host: 'localhost'
Radarr__Postgres__Port: '5432'
Radarr__Postgres__User: 'radarr'
Radarr__Postgres__Password: 'radarr'
pool:
vmImage: ${{ variables.linuxImage }}
steps:
- task: UseDotNet@2
displayName: 'Install .net core'
inputs:
version: $(dotnetVersion)
- checkout: none
- task: DownloadPipelineArtifact@2
displayName: Download Test Artifact
inputs:
buildType: 'current'
artifactName: 'linux-x64-tests'
targetPath: $(testsFolder)
- task: DownloadPipelineArtifact@2
displayName: Download Build Artifact
inputs:
buildType: 'current'
artifactName: Packages
itemPattern: '**/$(pattern)'
targetPath: $(Build.ArtifactStagingDirectory)
- task: ExtractFiles@1
inputs:
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
displayName: Extract Package
- bash: |
mkdir -p ./bin/
cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Radarr/. ./bin/
displayName: Move Package Contents
- bash: |
docker run -d --name=postgres15 \
-e POSTGRES_PASSWORD=radarr \
-e POSTGRES_USER=radarr \
-p 5432:5432/tcp \
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
postgres:15
displayName: Start postgres
- bash: |
chmod a+x ${TESTSFOLDER}/test.sh
${TESTSFOLDER}/test.sh Linux Integration Test
displayName: Run Integration Tests
- task: PublishTestResults@2
inputs:
testResultsFormat: 'NUnit'
testResultsFiles: '**/TestResult.xml'
testRunTitle: 'Integration LinuxCore Postgres15 Database Integration Tests'
testRunTitle: 'Integration LinuxCore Postgres Database Integration Tests'
failTaskOnFailedTests: true
displayName: Publish Test Results

View File

@@ -392,21 +392,22 @@ then
fi
fi
if [[ "$LINT" = "YES" || "$FRONTEND" = "YES" ]];
if [ "$FRONTEND" = "YES" ];
then
YarnInstall
RunWebpack
fi
if [ "$LINT" = "YES" ];
then
if [ -z "$FRONTEND" ];
then
YarnInstall
fi
LintUI
fi
if [ "$FRONTEND" = "YES" ];
then
RunWebpack
fi
if [ "$PACKAGES" = "YES" ];
then
UpdateVersionNumber

View File

@@ -4,14 +4,14 @@ module.exports = {
plugins: [
// Stage 1
'@babel/plugin-proposal-export-default-from',
['@babel/plugin-transform-optional-chaining', { loose }],
['@babel/plugin-transform-nullish-coalescing-operator', { loose }],
['@babel/plugin-proposal-optional-chaining', { loose }],
['@babel/plugin-proposal-nullish-coalescing-operator', { loose }],
// Stage 2
'@babel/plugin-transform-export-namespace-from',
'@babel/plugin-proposal-export-namespace-from',
// Stage 3
['@babel/plugin-transform-class-properties', { loose }],
['@babel/plugin-proposal-class-properties', { loose }],
'@babel/plugin-syntax-dynamic-import'
],
env: {

View File

@@ -36,7 +36,7 @@ module.exports = (env) => {
},
entry: {
index: 'index.ts'
index: 'index.js'
},
resolve: {
@@ -66,23 +66,23 @@ module.exports = (env) => {
output: {
path: distFolder,
publicPath: '/',
filename: '[name]-[contenthash].js',
filename: '[name].js',
sourceMapFilename: '[file].map'
},
optimization: {
moduleIds: 'deterministic',
chunkIds: isProduction ? 'deterministic' : 'named'
chunkIds: 'named',
splitChunks: {
chunks: 'initial',
name: 'vendors'
}
},
performance: {
hints: false
},
experiments: {
topLevelAwait: true
},
plugins: [
new webpack.DefinePlugin({
__DEV__: !isProduction,
@@ -90,15 +90,13 @@ module.exports = (env) => {
}),
new MiniCssExtractPlugin({
filename: 'Content/styles.css',
chunkFilename: 'Content/[id]-[chunkhash].css'
filename: 'Content/styles.css'
}),
new HtmlWebpackPlugin({
template: 'frontend/src/index.ejs',
filename: 'index.html',
publicPath: '/',
inject: false
publicPath: '/'
}),
new FileManagerPlugin({
@@ -235,7 +233,6 @@ module.exports = (env) => {
{
loader: 'url-loader',
options: {
limit: 10240,
mimetype: 'application/font-woff',
emitFile: false,
name: 'Content/Fonts/[name].[ext]'

View File

@@ -1,6 +1,5 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import PageContent from 'Components/Page/PageContent';
@@ -157,16 +156,16 @@ class Blocklist extends Component {
{
!isFetching && !!error &&
<Alert kind={kinds.DANGER}>
{translate('BlocklistLoadError')}
</Alert>
<div>
{translate('UnableToLoadBlocklist')}
</div>
}
{
isPopulated && !error && !items.length &&
<Alert kind={kinds.INFO}>
{translate('NoHistoryBlocklist')}
</Alert>
<div>
{translate('NoHistory')}
</div>
}
{
@@ -210,7 +209,7 @@ class Blocklist extends Component {
isOpen={isConfirmRemoveModalOpen}
kind={kinds.DANGER}
title={translate('RemoveSelected')}
message={translate('RemoveSelectedBlocklistMessageText')}
message={translate('AreYouSureYouWantToRemoveTheSelectedItemsFromBlocklist')}
confirmLabel={translate('RemoveSelected')}
onConfirm={this.onRemoveSelectedConfirmed}
onCancel={this.onConfirmRemoveModalClose}

View File

@@ -82,7 +82,7 @@ class BlocklistRow extends Component {
return null;
}
if (name === 'movieMetadata.sortTitle') {
if (name === 'movies.sortTitle') {
return (
<TableRowCell key={name}>
<MovieTitleLink

View File

@@ -7,7 +7,6 @@ import DescriptionListItemTitle from 'Components/DescriptionList/DescriptionList
import Link from 'Components/Link/Link';
import formatDateTime from 'Utilities/Date/formatDateTime';
import formatAge from 'Utilities/Number/formatAge';
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
import translate from 'Utilities/String/translate';
import styles from './HistoryDetails.css';
@@ -16,7 +15,6 @@ function HistoryDetails(props) {
eventType,
sourceTitle,
data,
downloadId,
shortDateFormat,
timeFormat
} = props;
@@ -25,11 +23,11 @@ function HistoryDetails(props) {
const {
indexer,
releaseGroup,
movieMatchType,
customFormatScore,
nzbInfoUrl,
downloadClient,
downloadClientName,
downloadId,
movieMatchType,
age,
ageHours,
ageMinutes,
@@ -47,31 +45,33 @@ function HistoryDetails(props) {
/>
{
indexer ?
!!indexer &&
<DescriptionListItem
title={translate('Indexer')}
data={indexer}
/> :
null
/>
}
{
releaseGroup ?
!!releaseGroup &&
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('ReleaseGroup')}
data={releaseGroup}
/> :
null
/>
}
{
customFormatScore && customFormatScore !== '0' ?
<DescriptionListItem
title={translate('CustomFormatScore')}
data={formatCustomFormatScore(customFormatScore)}
/> :
null
!!nzbInfoUrl &&
<span>
<DescriptionListItemTitle>
Info URL
</DescriptionListItemTitle>
<DescriptionListItemDescription>
<Link to={nzbInfoUrl}>{nzbInfoUrl}</Link>
</DescriptionListItemDescription>
</span>
}
{
@@ -84,20 +84,6 @@ function HistoryDetails(props) {
null
}
{
nzbInfoUrl ?
<span>
<DescriptionListItemTitle>
{translate('InfoUrl')}
</DescriptionListItemTitle>
<DescriptionListItemDescription>
<Link to={nzbInfoUrl}>{nzbInfoUrl}</Link>
</DescriptionListItemDescription>
</span> :
null
}
{
downloadClientNameInfo ?
<DescriptionListItem
@@ -108,30 +94,27 @@ function HistoryDetails(props) {
}
{
downloadId ?
!!downloadId &&
<DescriptionListItem
title={translate('GrabId')}
title={translate('GrabID')}
data={downloadId}
/> :
null
/>
}
{
indexer ?
!!indexer &&
<DescriptionListItem
title={translate('AgeWhenGrabbed')}
data={formatAge(age, ageHours, ageMinutes)}
/> :
null
/>
}
{
publishedDate ?
!!publishedDate &&
<DescriptionListItem
title={translate('PublishedDate')}
data={formatDateTime(publishedDate, shortDateFormat, timeFormat, { includeSeconds: true })}
/> :
null
/>
}
</DescriptionList>
);
@@ -151,21 +134,11 @@ function HistoryDetails(props) {
/>
{
downloadId ?
<DescriptionListItem
title={translate('GrabId')}
data={downloadId}
/> :
null
}
{
message ?
!!message &&
<DescriptionListItem
title={translate('Message')}
data={message}
/> :
null
/>
}
</DescriptionList>
);
@@ -173,7 +146,6 @@ function HistoryDetails(props) {
if (eventType === 'downloadFolderImported') {
const {
customFormatScore,
droppedPath,
importedPath
} = data;
@@ -187,32 +159,21 @@ function HistoryDetails(props) {
/>
{
droppedPath ?
!!droppedPath &&
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('Source')}
data={droppedPath}
/> :
null
/>
}
{
importedPath ?
!!importedPath &&
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('ImportedTo')}
data={importedPath}
/> :
null
}
{
customFormatScore && customFormatScore !== '0' ?
<DescriptionListItem
title={translate('CustomFormatScore')}
data={formatCustomFormatScore(customFormatScore)}
/> :
null
/>
}
</DescriptionList>
);
@@ -220,21 +181,20 @@ function HistoryDetails(props) {
if (eventType === 'movieFileDeleted') {
const {
reason,
customFormatScore
reason
} = data;
let reasonMessage = '';
switch (reason) {
case 'Manual':
reasonMessage = translate('DeletedReasonManual');
reasonMessage = translate('FileWasDeletedByViaUI');
break;
case 'MissingFromDisk':
reasonMessage = translate('DeletedReasonMissingFromDisk');
reasonMessage = translate('MissingFromDisk');
break;
case 'Upgrade':
reasonMessage = translate('DeletedReasonUpgrade');
reasonMessage = translate('FileWasDeletedByUpgrade');
break;
default:
reasonMessage = '';
@@ -251,15 +211,6 @@ function HistoryDetails(props) {
title={translate('Reason')}
data={reasonMessage}
/>
{
customFormatScore && customFormatScore !== '0' ?
<DescriptionListItem
title={translate('CustomFormatScore')}
data={formatCustomFormatScore(customFormatScore)}
/> :
null
}
</DescriptionList>
);
}
@@ -311,21 +262,11 @@ function HistoryDetails(props) {
/>
{
downloadId ?
<DescriptionListItem
title={translate('GrabId')}
data={downloadId}
/> :
null
}
{
message ?
!!message &&
<DescriptionListItem
title={translate('Message')}
data={message}
/> :
null
/>
}
</DescriptionList>
);
@@ -346,7 +287,6 @@ HistoryDetails.propTypes = {
eventType: PropTypes.string.isRequired,
sourceTitle: PropTypes.string.isRequired,
data: PropTypes.object.isRequired,
downloadId: PropTypes.string,
shortDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired
};

View File

@@ -15,19 +15,19 @@ import styles from './HistoryDetailsModal.css';
function getHeaderTitle(eventType) {
switch (eventType) {
case 'grabbed':
return translate('Grabbed');
return 'Grabbed';
case 'downloadFailed':
return translate('DownloadFailed');
return 'Download Failed';
case 'downloadFolderImported':
return translate('MovieImported');
return 'Movie Imported';
case 'movieFileDeleted':
return translate('MovieFileDeleted');
return 'Movie File Deleted';
case 'movieFileRenamed':
return translate('MovieFileRenamed');
return 'Movie File Renamed';
case 'downloadIgnored':
return translate('DownloadIgnored');
return 'Download Ignored';
default:
return translate('Unknown');
return 'Unknown';
}
}
@@ -37,7 +37,6 @@ function HistoryDetailsModal(props) {
eventType,
sourceTitle,
data,
downloadId,
isMarkingAsFailed,
shortDateFormat,
timeFormat,
@@ -60,7 +59,6 @@ function HistoryDetailsModal(props) {
eventType={eventType}
sourceTitle={sourceTitle}
data={data}
downloadId={downloadId}
shortDateFormat={shortDateFormat}
timeFormat={timeFormat}
/>
@@ -75,7 +73,7 @@ function HistoryDetailsModal(props) {
isSpinning={isMarkingAsFailed}
onPress={onMarkAsFailedPress}
>
{translate('MarkAsFailed')}
Mark as Failed
</SpinnerButton>
}
@@ -95,7 +93,6 @@ HistoryDetailsModal.propTypes = {
eventType: PropTypes.string.isRequired,
sourceTitle: PropTypes.string.isRequired,
data: PropTypes.object.isRequired,
downloadId: PropTypes.string,
isMarkingAsFailed: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired,

View File

@@ -1,6 +1,5 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import FilterMenu from 'Components/Menu/FilterMenu';
import PageContent from 'Components/Page/PageContent';
@@ -12,7 +11,7 @@ import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import TablePager from 'Components/Table/TablePager';
import { align, icons, kinds } from 'Helpers/Props';
import { align, icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import HistoryRowConnector from './HistoryRowConnector';
@@ -84,9 +83,9 @@ class History extends Component {
{
!isFetchingAny && hasError &&
<Alert kind={kinds.DANGER}>
{translate('HistoryLoadError')}
</Alert>
<div>
{translate('UnableToLoadHistory')}
</div>
}
{
@@ -94,9 +93,9 @@ class History extends Component {
// wait for the episodes to populate because they are never coming.
isPopulated && !hasError && !items.length &&
<Alert kind={kinds.INFO}>
{translate('NoHistoryFound')}
</Alert>
<div>
{translate('NoHistory')}
</div>
}
{

View File

@@ -3,7 +3,6 @@ import React from 'react';
import Icon from 'Components/Icon';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './HistoryEventTypeCell.css';
function getIconName(eventType) {
@@ -39,21 +38,21 @@ function getIconKind(eventType) {
function getTooltip(eventType, data) {
switch (eventType) {
case 'grabbed':
return translate('MovieGrabbedHistoryTooltip', { indexer: data.indexer, downloadClient: data.downloadClient });
return `Movie grabbed from ${data.indexer} and sent to ${data.downloadClient}`;
case 'movieFolderImported':
return translate('MovieFolderImportedTooltip');
return 'Movie imported from movie folder';
case 'downloadFolderImported':
return translate('MovieImportedTooltip');
return 'Movie downloaded successfully and picked up from download client';
case 'downloadFailed':
return translate('MovieDownloadFailedTooltip');
return 'Movie download failed';
case 'movieFileDeleted':
return translate('MovieFileDeletedTooltip');
return 'Movie file deleted';
case 'movieFileRenamed':
return translate('MovieFileRenamedTooltip');
return 'Movie file renamed';
case 'downloadIgnored':
return translate('MovieDownloadIgnoredTooltip');
return 'Movie Download Ignored';
default:
return translate('UnknownEventTooltip');
return 'Unknown event';
}
}

View File

@@ -4,8 +4,7 @@ import IconButton from 'Components/Link/IconButton';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow';
import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, tooltipPositions } from 'Helpers/Props';
import { icons } from 'Helpers/Props';
import MovieFormats from 'Movie/MovieFormats';
import MovieLanguage from 'Movie/MovieLanguage';
import MovieQuality from 'Movie/MovieQuality';
@@ -64,7 +63,6 @@ class HistoryRow extends Component {
sourceTitle,
date,
data,
downloadId,
isMarkingAsFailed,
columns,
shortDateFormat,
@@ -99,7 +97,7 @@ class HistoryRow extends Component {
);
}
if (name === 'movieMetadata.sortTitle') {
if (name === 'movies.sortTitle') {
return (
<TableRowCell key={name}>
<MovieTitleLink
@@ -178,14 +176,7 @@ class HistoryRow extends Component {
key={name}
className={styles.customFormatScore}
>
<Tooltip
anchor={formatCustomFormatScore(
customFormatScore,
customFormats.length
)}
tooltip={<MovieFormats formats={customFormats} />}
position={tooltipPositions.BOTTOM}
/>
{formatCustomFormatScore(customFormatScore)}
</TableRowCell>
);
}
@@ -217,12 +208,10 @@ class HistoryRow extends Component {
key={name}
className={styles.details}
>
<div className={styles.actionContents}>
<IconButton
name={icons.INFO}
onPress={this.onDetailsPress}
/>
</div>
<IconButton
name={icons.INFO}
onPress={this.onDetailsPress}
/>
</TableRowCell>
);
}
@@ -236,7 +225,6 @@ class HistoryRow extends Component {
eventType={eventType}
sourceTitle={sourceTitle}
data={data}
downloadId={downloadId}
isMarkingAsFailed={isMarkingAsFailed}
shortDateFormat={shortDateFormat}
timeFormat={timeFormat}
@@ -261,7 +249,6 @@ HistoryRow.propTypes = {
sourceTitle: PropTypes.string.isRequired,
date: PropTypes.string.isRequired,
data: PropTypes.object.isRequired,
downloadId: PropTypes.string,
isMarkingAsFailed: PropTypes.bool,
markAsFailedError: PropTypes.object,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
@@ -270,8 +257,4 @@ HistoryRow.propTypes = {
onMarkAsFailedPress: PropTypes.func.isRequired
};
HistoryRow.defaultProps = {
customFormats: []
};
export default HistoryRow;

View File

@@ -1,7 +1,6 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
@@ -13,7 +12,7 @@ import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import TablePager from 'Components/Table/TablePager';
import { align, icons, kinds } from 'Helpers/Props';
import { align, icons } from 'Helpers/Props';
import getRemovedItems from 'Utilities/Object/getRemovedItems';
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
import translate from 'Utilities/String/translate';
@@ -232,17 +231,17 @@ class Queue extends Component {
{
!isRefreshing && hasError ?
<Alert kind={kinds.DANGER}>
{translate('QueueLoadError')}
</Alert> :
<div>
{translate('FailedToLoadQueue')}
</div> :
null
}
{
isAllPopulated && !hasError && !items.length ?
<Alert kind={kinds.INFO}>
<div>
{translate('QueueIsEmpty')}
</Alert> :
</div> :
null
}

View File

@@ -1,5 +0,0 @@
.progressBarContainer {
display: flex;
justify-content: center;
width: 100%;
}

View File

@@ -1,7 +0,0 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'progressBarContainer': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@@ -1,71 +1,116 @@
import moment from 'moment';
import PropTypes from 'prop-types';
import React from 'react';
import Icon from 'Components/Icon';
import Popover from 'Components/Tooltip/Popover';
import { icons, tooltipPositions } from 'Helpers/Props';
import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import QueueStatus from './QueueStatus';
import styles from './QueueDetails.css';
function QueueDetails(props) {
const {
title,
size,
sizeleft,
estimatedCompletionTime,
status,
trackedDownloadState,
trackedDownloadStatus,
statusMessages,
errorMessage,
progressBar
} = props;
const progress = size ? (100 - sizeleft / size * 100) : 0;
const isDownloading = status === 'downloading';
const isPaused = status === 'paused';
const hasWarning = trackedDownloadStatus === 'warning';
const hasError = trackedDownloadStatus === 'error';
if (
(isDownloading || isPaused) &&
!hasWarning &&
!hasError
) {
const state = isPaused ? translate('Paused') : translate('Downloading');
if (progress < 5) {
return (
<Icon
name={icons.DOWNLOADING}
title={`${state} - ${progress.toFixed(1)}% ${title}`}
/>
);
}
if (status === 'pending') {
return (
<Popover
className={styles.progressBarContainer}
anchor={progressBar}
title={`${state} - ${progress.toFixed(1)}%`}
body={
<div>{title}</div>
}
position={tooltipPositions.LEFT}
<Icon
name={icons.PENDING}
title={translate('ReleaseWillBeProcessedInterp', [moment(estimatedCompletionTime).fromNow()])}
/>
);
}
return (
<QueueStatus
sourceTitle={title}
status={status}
trackedDownloadStatus={trackedDownloadStatus}
trackedDownloadState={trackedDownloadState}
statusMessages={statusMessages}
errorMessage={errorMessage}
position={tooltipPositions.LEFT}
/>
);
if (status === 'completed') {
if (errorMessage) {
return (
<Icon
name={icons.DOWNLOAD}
kind={kinds.DANGER}
title={translate('ImportFailedInterp', [errorMessage])}
/>
);
}
if (trackedDownloadStatus === 'warning') {
return (
<Icon
name={icons.DOWNLOAD}
kind={kinds.WARNING}
title={translate('UnableToImportCheckLogs')}
/>
);
}
if (trackedDownloadState === 'importPending') {
return (
<Icon
name={icons.DOWNLOAD}
kind={kinds.PURPLE}
title={`${translate('Downloaded')} - ${translate('WaitingToImport')}`}
/>
);
}
if (trackedDownloadState === 'importing') {
return (
<Icon
name={icons.DOWNLOAD}
kind={kinds.PURPLE}
title={`${translate('Downloaded')} - ${translate('Importing')}`}
/>
);
}
}
if (errorMessage) {
return (
<Icon
name={icons.DOWNLOADING}
kind={kinds.DANGER}
title={translate('DownloadFailedInterp', [errorMessage])}
/>
);
}
if (status === 'failed') {
return (
<Icon
name={icons.DOWNLOADING}
kind={kinds.DANGER}
title={translate('DownloadFailedCheckDownloadClientForMoreDetails')}
/>
);
}
if (status === 'warning') {
return (
<Icon
name={icons.DOWNLOADING}
kind={kinds.WARNING}
title={translate('DownloadWarningCheckDownloadClientForMoreDetails')}
/>
);
}
if (progress < 5) {
return (
<Icon
name={icons.DOWNLOADING}
title={translate('MovieIsDownloadingInterp', [progress.toFixed(1), title])}
/>
);
}
return progressBar;
}
QueueDetails.propTypes = {
@@ -76,7 +121,6 @@ QueueDetails.propTypes = {
status: PropTypes.string.isRequired,
trackedDownloadState: PropTypes.string.isRequired,
trackedDownloadStatus: PropTypes.string.isRequired,
statusMessages: PropTypes.arrayOf(PropTypes.object),
errorMessage: PropTypes.string,
progressBar: PropTypes.node.isRequired
};

View File

@@ -61,7 +61,7 @@ class QueueOptions extends Component {
type={inputTypes.CHECK}
name="includeUnknownMovieItems"
value={includeUnknownMovieItems}
helpText={translate('ShowUnknownMovieItemsHelpText')}
helpText={translate('IncludeUnknownMovieItemsHelpText')}
onChange={this.onOptionChange}
/>
</FormGroup>

View File

@@ -16,12 +16,6 @@
width: 150px;
}
.customFormatScore {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 55px;
}
.actions {
composes: cell from '~Components/Table/Cells/TableRowCell.css';

View File

@@ -2,7 +2,6 @@
// Please do not change this file!
interface CssExports {
'actions': string;
'customFormatScore': string;
'progress': string;
'protocol': string;
'quality': string;

View File

@@ -8,15 +8,13 @@ import ProgressBar from 'Components/ProgressBar';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import TableRow from 'Components/Table/TableRow';
import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import { icons, kinds } from 'Helpers/Props';
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
import MovieFormats from 'Movie/MovieFormats';
import MovieLanguage from 'Movie/MovieLanguage';
import MovieQuality from 'Movie/MovieQuality';
import MovieTitleLink from 'Movie/MovieTitleLink';
import formatBytes from 'Utilities/Number/formatBytes';
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
import translate from 'Utilities/String/translate';
import QueueStatusCell from './QueueStatusCell';
import RemoveQueueItemModal from './RemoveQueueItemModal';
@@ -44,14 +42,14 @@ class QueueRow extends Component {
this.setState({ isRemoveQueueItemModalOpen: true });
};
onRemoveQueueItemModalConfirmed = (blocklist, skipRedownload) => {
onRemoveQueueItemModalConfirmed = (blocklist) => {
const {
onRemoveQueueItemPress,
onQueueRowModalOpenOrClose
} = this.props;
onQueueRowModalOpenOrClose(false);
onRemoveQueueItemPress(blocklist, skipRedownload);
onRemoveQueueItemPress(blocklist);
this.setState({ isRemoveQueueItemModalOpen: false });
};
@@ -90,7 +88,6 @@ class QueueRow extends Component {
movie,
quality,
customFormats,
customFormatScore,
languages,
protocol,
indexer,
@@ -204,24 +201,6 @@ class QueueRow extends Component {
);
}
if (name === 'customFormatScore') {
return (
<TableRowCell
key={name}
className={styles.customFormatScore}
>
<Tooltip
anchor={formatCustomFormatScore(
customFormatScore,
customFormats.length
)}
tooltip={<MovieFormats formats={customFormats} />}
position={tooltipPositions.BOTTOM}
/>
</TableRowCell>
);
}
if (name === 'protocol') {
return (
<TableRowCell key={name}>
@@ -386,7 +365,6 @@ QueueRow.propTypes = {
movie: PropTypes.object,
quality: PropTypes.object.isRequired,
customFormats: PropTypes.arrayOf(PropTypes.object),
customFormatScore: PropTypes.number.isRequired,
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
protocol: PropTypes.string.isRequired,
indexer: PropTypes.string,
@@ -412,7 +390,6 @@ QueueRow.propTypes = {
};
QueueRow.defaultProps = {
customFormats: [],
isGrabbing: false,
isRemoving: false
};

View File

@@ -1,3 +0,0 @@
.noMessages {
margin-bottom: 10px;
}

View File

@@ -1,162 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import Icon from 'Components/Icon';
import Popover from 'Components/Tooltip/Popover';
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './QueueStatus.css';
function getDetailedPopoverBody(statusMessages) {
return (
<div>
{
statusMessages.map(({ title, messages }) => {
return (
<div
key={title}
className={messages.length ? undefined: styles.noMessages}
>
{title}
<ul>
{
messages.map((message) => {
return (
<li key={message}>
{message}
</li>
);
})
}
</ul>
</div>
);
})
}
</div>
);
}
function QueueStatus(props) {
const {
sourceTitle,
status,
trackedDownloadStatus,
trackedDownloadState,
statusMessages,
errorMessage,
position,
canFlip
} = props;
const hasWarning = trackedDownloadStatus === 'warning';
const hasError = trackedDownloadStatus === 'error';
// status === 'downloading'
let iconName = icons.DOWNLOADING;
let iconKind = kinds.DEFAULT;
let title = translate('Downloading');
if (status === 'paused') {
iconName = icons.PAUSED;
title = translate('Paused');
}
if (status === 'queued') {
iconName = icons.QUEUED;
title = translate('Queued');
}
if (status === 'completed') {
iconName = icons.DOWNLOADED;
title = translate('Downloaded');
if (trackedDownloadState === 'importPending') {
title += ` - ${translate('WaitingToImport')}`;
iconKind = kinds.PURPLE;
}
if (trackedDownloadState === 'importing') {
title += ` - ${translate('Importing')}`;
iconKind = kinds.PURPLE;
}
if (trackedDownloadState === 'failedPending') {
title += ` - ${translate('WaitingToProcess')}`;
iconKind = kinds.DANGER;
}
}
if (hasWarning) {
iconKind = kinds.WARNING;
}
if (status === 'delay') {
iconName = icons.PENDING;
title = translate('Pending');
}
if (status === 'downloadClientUnavailable') {
iconName = icons.PENDING;
iconKind = kinds.WARNING;
title = translate('PendingDownloadClientUnavailable');
}
if (status === 'failed') {
iconName = icons.DOWNLOADING;
iconKind = kinds.DANGER;
title = translate('DownloadFailed');
}
if (status === 'warning') {
iconName = icons.DOWNLOADING;
iconKind = kinds.WARNING;
const warningMessage = errorMessage || translate('CheckDownloadClientForDetails');
title = translate('DownloadWarning', { warningMessage });
}
if (hasError) {
if (status === 'completed') {
iconName = icons.DOWNLOAD;
iconKind = kinds.DANGER;
title = translate('ImportFailed', { sourceTitle });
} else {
iconName = icons.DOWNLOADING;
iconKind = kinds.DANGER;
title = translate('DownloadFailed');
}
}
return (
<Popover
anchor={
<Icon
name={iconName}
kind={iconKind}
/>
}
title={title}
body={hasWarning || hasError ? getDetailedPopoverBody(statusMessages) : sourceTitle}
position={position}
canFlip={canFlip}
/>
);
}
QueueStatus.propTypes = {
sourceTitle: PropTypes.string.isRequired,
status: PropTypes.string.isRequired,
trackedDownloadStatus: PropTypes.string.isRequired,
trackedDownloadState: PropTypes.string.isRequired,
statusMessages: PropTypes.arrayOf(PropTypes.object),
errorMessage: PropTypes.string,
position: PropTypes.oneOf(tooltipPositions.all).isRequired,
canFlip: PropTypes.bool.isRequired
};
QueueStatus.defaultProps = {
trackedDownloadStatus: 'ok',
trackedDownloadState: 'downloading',
canFlip: false
};
export default QueueStatus;

View File

@@ -1,11 +1,39 @@
import PropTypes from 'prop-types';
import React from 'react';
import Icon from 'Components/Icon';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import { tooltipPositions } from 'Helpers/Props';
import Popover from 'Components/Tooltip/Popover';
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import QueueStatus from './QueueStatus';
import styles from './QueueStatusCell.css';
function getDetailedPopoverBody(statusMessages) {
return (
<div>
{
statusMessages.map(({ title, messages }) => {
return (
<div key={title}>
{title}
<ul>
{
messages.map((message) => {
return (
<li key={message}>
{message}
</li>
);
})
}
</ul>
</div>
);
})
}
</div>
);
}
function QueueStatusCell(props) {
const {
sourceTitle,
@@ -16,16 +44,97 @@ function QueueStatusCell(props) {
errorMessage
} = props;
const hasWarning = trackedDownloadStatus === 'warning';
const hasError = trackedDownloadStatus === 'error';
// status === 'downloading'
let iconName = icons.DOWNLOADING;
let iconKind = kinds.DEFAULT;
let title = translate('Downloading');
if (status === 'paused') {
iconName = icons.PAUSED;
title = translate('Paused');
}
if (status === 'queued') {
iconName = icons.QUEUED;
title = translate('Queued');
}
if (status === 'completed') {
iconName = icons.DOWNLOADED;
title = translate('Downloaded');
if (trackedDownloadState === 'importPending') {
title += ` - ${translate('WaitingToImport')}`;
iconKind = kinds.PURPLE;
}
if (trackedDownloadState === 'importing') {
title += ` - ${translate('Importing')}`;
iconKind = kinds.PURPLE;
}
if (trackedDownloadState === 'failedPending') {
title += ` - ${translate('WaitingToProcess')}`;
iconKind = kinds.DANGER;
}
}
if (hasWarning) {
iconKind = kinds.WARNING;
}
if (status === 'delay') {
iconName = icons.PENDING;
title = translate('Pending');
}
if (status === 'DownloadClientUnavailable') {
iconName = icons.PENDING;
iconKind = kinds.WARNING;
title = `${translate('Pending')} - ${translate('DownloadClientUnavailable')}`;
}
if (status === 'failed') {
iconName = icons.DOWNLOADING;
iconKind = kinds.DANGER;
title = translate('DownloadFailed');
}
if (status === 'warning') {
iconName = icons.DOWNLOADING;
iconKind = kinds.WARNING;
const warningMessage = errorMessage || translate('CheckDownloadClientForDetails');
title = translate('DownloadWarning', [warningMessage]);
}
if (hasError) {
if (status === 'completed') {
iconName = icons.DOWNLOAD;
iconKind = kinds.DANGER;
title = translate('ImportFailed', [sourceTitle]);
} else {
iconName = icons.DOWNLOADING;
iconKind = kinds.DANGER;
title = translate('DownloadFailed');
}
}
return (
<TableRowCell className={styles.status}>
<QueueStatus
sourceTitle={sourceTitle}
status={status}
trackedDownloadStatus={trackedDownloadStatus}
trackedDownloadState={trackedDownloadState}
statusMessages={statusMessages}
errorMessage={errorMessage}
<Popover
anchor={
<Icon
name={iconName}
kind={iconKind}
/>
}
title={title}
body={hasWarning || hasError ? getDetailedPopoverBody(statusMessages) : sourceTitle}
position={tooltipPositions.RIGHT}
canFlip={false}
/>
</TableRowCell>
);

View File

@@ -22,8 +22,7 @@ class RemoveQueueItemModal extends Component {
this.state = {
remove: true,
blocklist: false,
skipRedownload: false
blocklist: false
};
}
@@ -33,8 +32,7 @@ class RemoveQueueItemModal extends Component {
resetState = function() {
this.setState({
remove: true,
blocklist: false,
skipRedownload: false
blocklist: false
});
};
@@ -49,10 +47,6 @@ class RemoveQueueItemModal extends Component {
this.setState({ blocklist: value });
};
onSkipRedownloadChange = ({ value }) => {
this.setState({ skipRedownload: value });
};
onRemoveConfirmed = () => {
const state = this.state;
@@ -76,7 +70,7 @@ class RemoveQueueItemModal extends Component {
isPending
} = this.props;
const { remove, blocklist, skipRedownload } = this.state;
const { remove, blocklist } = this.state;
return (
<Modal
@@ -88,12 +82,12 @@ class RemoveQueueItemModal extends Component {
onModalClose={this.onModalClose}
>
<ModalHeader>
{translate('RemoveQueueItem', { sourceTitle })}
{translate('Remove')} - {sourceTitle}
</ModalHeader>
<ModalBody>
<div>
{translate('RemoveQueueItemConfirmation', { sourceTitle })}
{translate('RemoveFromQueueText', [sourceTitle])}
</div>
{
@@ -106,7 +100,7 @@ class RemoveQueueItemModal extends Component {
type={inputTypes.CHECK}
name="remove"
value={remove}
helpTextWarning={translate('RemoveFromDownloadClientHelpTextWarning')}
helpTextWarning={translate('RemoveHelpTextWarning')}
isDisabled={!canIgnore}
onChange={this.onRemoveChange}
/>
@@ -115,30 +109,15 @@ class RemoveQueueItemModal extends Component {
<FormGroup>
<FormLabel>{translate('BlocklistRelease')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="blocklist"
value={blocklist}
helpText={translate('BlocklistReleaseHelpText')}
helpText={translate('BlocklistHelpText')}
onChange={this.onBlocklistChange}
/>
</FormGroup>
{
blocklist ?
<FormGroup>
<FormLabel>{translate('SkipRedownload')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="skipRedownload"
value={skipRedownload}
helpText={translate('SkipRedownloadHelpText')}
onChange={this.onSkipRedownloadChange}
/>
</FormGroup> :
null
}
</ModalBody>
<ModalFooter>
@@ -150,7 +129,7 @@ class RemoveQueueItemModal extends Component {
kind={kinds.DANGER}
onPress={this.onRemoveConfirmed}
>
{translate('Remove')}
Remove
</Button>
</ModalFooter>
</ModalContent>

View File

@@ -23,8 +23,7 @@ class RemoveQueueItemsModal extends Component {
this.state = {
remove: true,
blocklist: false,
skipRedownload: false
blocklist: false
};
}
@@ -34,8 +33,7 @@ class RemoveQueueItemsModal extends Component {
resetState = function() {
this.setState({
remove: true,
blocklist: false,
skipRedownload: false
blocklist: false
});
};
@@ -50,10 +48,6 @@ class RemoveQueueItemsModal extends Component {
this.setState({ blocklist: value });
};
onSkipRedownloadChange = ({ value }) => {
this.setState({ skipRedownload: value });
};
onRemoveConfirmed = () => {
const state = this.state;
@@ -77,7 +71,7 @@ class RemoveQueueItemsModal extends Component {
allPending
} = this.props;
const { remove, blocklist, skipRedownload } = this.state;
const { remove, blocklist } = this.state;
return (
<Modal
@@ -94,7 +88,7 @@ class RemoveQueueItemsModal extends Component {
<ModalBody>
<div className={styles.message}>
{selectedCount > 1 ? translate('RemoveSelectedItemsQueueMessageText', { selectedCount }) : translate('RemoveSelectedItemQueueMessageText')}
{selectedCount > 1 ? translate('AreYouSureYouWantToRemoveSelectedItemsFromQueue', selectedCount) : translate('AreYouSureYouWantToRemoveSelectedItemFromQueue')}
</div>
{
@@ -123,25 +117,11 @@ class RemoveQueueItemsModal extends Component {
type={inputTypes.CHECK}
name="blocklist"
value={blocklist}
helpText={translate('BlocklistReleaseHelpText')}
helpText={translate('BlocklistHelpText')}
onChange={this.onBlocklistChange}
/>
</FormGroup>
{
blocklist ?
<FormGroup>
<FormLabel>{translate('SkipRedownload')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="skipRedownload"
value={skipRedownload}
helpText={translate('SkipRedownloadHelpText')}
onChange={this.onSkipRedownloadChange}
/>
</FormGroup> :
null
}
</ModalBody>
<ModalFooter>
@@ -153,7 +133,7 @@ class RemoveQueueItemsModal extends Component {
kind={kinds.DANGER}
onPress={this.onRemoveConfirmed}
>
{translate('Remove')}
Remove
</Button>
</ModalFooter>
</ModalContent>

View File

@@ -27,7 +27,7 @@ function TimeleftCell(props) {
return (
<TableRowCell
className={styles.timeleft}
title={translate('DelayingDownloadUntil', { date, time })}
title={translate('DelayingDownloadUntilInterp', [date, time])}
>
-
</TableRowCell>
@@ -41,7 +41,7 @@ function TimeleftCell(props) {
return (
<TableRowCell
className={styles.timeleft}
title={translate('RetryingDownloadOn', { date, time })}
title={translate('RetryingDownloadInterp', [date, time])}
>
-
</TableRowCell>

View File

@@ -155,7 +155,7 @@ class AddNewMovie extends Component {
!isFetching && !error && !items.length && !!term &&
<div className={styles.message}>
<div className={styles.noResults}>
{translate('CouldNotFindResults', { term })}
{translate('CouldNotFindResults', [term])}
</div>
<div>
{translate('YouCanAlsoSearch')}

View File

@@ -1,11 +1,9 @@
import { reduce } from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
@@ -107,9 +105,9 @@ class ImportMovie extends Component {
{
!rootFoldersFetching && !!rootFoldersError ?
<Alert kind={kinds.DANGER}>
<div>
{translate('UnableToLoadRootFolders')}
</Alert> :
</div> :
null
}
@@ -118,9 +116,9 @@ class ImportMovie extends Component {
!rootFoldersFetching &&
rootFoldersPopulated &&
!unmappedFolders.length ?
<Alert kind={kinds.INFO}>
{translate('AllMoviesInPathHaveBeenImported', { path })}
</Alert> :
<div>
{translate('AllMoviesInPathHaveBeenImported', [path])}
</div> :
null
}

View File

@@ -18,17 +18,17 @@ import styles from './ImportMovieSelectFolder.css';
const rootFolderColumns = [
{
name: 'path',
label: () => translate('Path'),
label: translate('Path'),
isVisible: true
},
{
name: 'freeSpace',
label: () => translate('FreeSpace'),
label: translate('FreeSpace'),
isVisible: true
},
{
name: 'unmappedFolders',
label: () => translate('UnmappedFolders'),
label: translate('UnmappedFolders'),
isVisible: true
},
{
@@ -92,9 +92,9 @@ class ImportMovieSelectFolder extends Component {
{
!isFetching && error ?
<Alert kind={kinds.DANGER}>
<div>
{translate('UnableToLoadRootFolders')}
</Alert> :
</div> :
null
}

View File

@@ -13,7 +13,7 @@ import Switch from 'Components/Router/Switch';
import DiscoverMovieConnector from 'DiscoverMovie/DiscoverMovieConnector';
import MovieDetailsPageConnector from 'Movie/Details/MovieDetailsPageConnector';
import MovieIndex from 'Movie/Index/MovieIndex';
import CustomFormatSettingsPage from 'Settings/CustomFormats/CustomFormatSettingsPage';
import CustomFormatSettingsConnector from 'Settings/CustomFormats/CustomFormatSettingsConnector';
import DownloadClientSettingsConnector from 'Settings/DownloadClients/DownloadClientSettingsConnector';
import GeneralSettingsConnector from 'Settings/General/GeneralSettingsConnector';
import ImportListSettingsConnector from 'Settings/ImportLists/ImportListSettingsConnector';
@@ -148,7 +148,7 @@ function AppRoutes(props) {
<Route
path="/settings/customformats"
component={CustomFormatSettingsPage}
component={CustomFormatSettingsConnector}
/>
<Route

View File

@@ -1,7 +1,6 @@
.version {
margin: 0 3px;
font-weight: bold;
font-family: var(--defaultFontFamily);
}
.maintenance {

View File

@@ -2,7 +2,6 @@ import PropTypes from 'prop-types';
import React from 'react';
import Button from 'Components/Link/Button';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
@@ -65,20 +64,20 @@ function AppUpdatedModalContent(props) {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{translate('AppUpdated', { appName: 'Radarr' })}
{translate('RadarrUpdated')}
</ModalHeader>
<ModalBody>
<div>
<InlineMarkdown data={translate('AppUpdatedVersion', { appName: 'Radarr', version })} blockClassName={styles.version} />
</div>
<div dangerouslySetInnerHTML={{ __html: translate('VersionUpdateText', [`<span className=${styles.version}>${version}</span>`]) }} />
{
isPopulated && !error && !!update &&
<div>
{
!update.changes &&
<div className={styles.maintenance}>{translate('MaintenanceRelease')}</div>
<div className={styles.maintenance}>
{translate('MaintenanceRelease')}
</div>
}
{

View File

@@ -28,11 +28,11 @@ function ConnectionLostModal(props) {
<ModalBody>
<div>
{translate('ConnectionLostToBackend', { appName: 'Radarr' })}
{translate('ConnectionLostMessage')}
</div>
<div className={styles.automatic}>
{translate('ConnectionLostReconnect', { appName: 'Radarr' })}
{translate('ConnectionLostAutomaticMessage')}
</div>
</ModalBody>
<ModalFooter>

View File

@@ -1,14 +1,8 @@
import InteractiveImportAppState from 'App/State/InteractiveImportAppState';
import CalendarAppState from './CalendarAppState';
import CommandAppState from './CommandAppState';
import MovieCollectionAppState from './MovieCollectionAppState';
import MovieFilesAppState from './MovieFilesAppState';
import MoviesAppState, { MovieIndexAppState } from './MoviesAppState';
import ParseAppState from './ParseAppState';
import QueueAppState from './QueueAppState';
import RootFolderAppState from './RootFolderAppState';
import SettingsAppState from './SettingsAppState';
import SystemAppState from './SystemAppState';
import TagsAppState from './TagsAppState';
interface FilterBuilderPropOption {
@@ -44,19 +38,13 @@ export interface CustomFilter {
}
interface AppState {
calendar: CalendarAppState;
commands: CommandAppState;
interactiveImport: InteractiveImportAppState;
movieCollections: MovieCollectionAppState;
movieFiles: MovieFilesAppState;
interactiveImport: InteractiveImportAppState;
movieIndex: MovieIndexAppState;
movies: MoviesAppState;
parse: ParseAppState;
queue: QueueAppState;
rootFolders: RootFolderAppState;
settings: SettingsAppState;
system: SystemAppState;
movies: MoviesAppState;
tags: TagsAppState;
queue: QueueAppState;
}
export default AppState;

View File

@@ -1,9 +0,0 @@
import AppSectionState from 'App/State/AppSectionState';
import Movie from 'Movie/Movie';
import { FilterBuilderProp } from './AppState';
interface CalendarAppState extends AppSectionState<Movie> {
filterBuilderProps: FilterBuilderProp<Movie>[];
}
export default CalendarAppState;

View File

@@ -1,6 +0,0 @@
import AppSectionState from 'App/State/AppSectionState';
import Command from 'Commands/Command';
export type CommandAppState = AppSectionState<Command>;
export default CommandAppState;

View File

@@ -1,7 +1,7 @@
import AppSectionState from 'App/State/AppSectionState';
import RecentFolder from 'InteractiveImport/Folder/RecentFolder';
import ImportMode from 'InteractiveImport/ImportMode';
import InteractiveImport from 'InteractiveImport/InteractiveImport';
import ImportMode from '../../InteractiveImport/ImportMode';
import InteractiveImport from '../../InteractiveImport/InteractiveImport';
interface InteractiveImportAppState extends AppSectionState<InteractiveImport> {
originalItems: InteractiveImport[];

View File

@@ -1,6 +0,0 @@
import AppSectionState from 'App/State/AppSectionState';
import MovieCollection from 'typings/MovieCollection';
type MovieCollectionAppState = AppSectionState<MovieCollection>;
export default MovieCollectionAppState;

View File

@@ -22,9 +22,6 @@ export interface MovieIndexAppState {
showQualityProfile: boolean;
showReleaseDate: boolean;
showCinemaRelease: boolean;
showTmdbRating: boolean;
showImdbRating: boolean;
showRottenTomatoesRating: boolean;
showSearchAction: boolean;
};

View File

@@ -1,34 +0,0 @@
import ModelBase from 'App/ModelBase';
import { AppSectionItemState } from 'App/State/AppSectionState';
import Language from 'Language/Language';
import Movie from 'Movie/Movie';
import { QualityModel } from 'Quality/Quality';
import CustomFormat from 'typings/CustomFormat';
export interface ParsedMovieInfo {
releaseTitle: string;
originalTitle: string;
movieTitle: string;
movieTitles: string[];
year: number;
quality: QualityModel;
languages: Language[];
releaseHash: string;
releaseGroup?: string;
edition?: string;
tmdbId?: number;
imdbId?: string;
}
export interface ParseModel extends ModelBase {
title: string;
parsedMovieInfo: ParsedMovieInfo;
movie?: Movie;
languages?: Language[];
customFormats?: CustomFormat[];
customFormatScore?: number;
}
type ParseAppState = AppSectionItemState<ParseModel>;
export default ParseAppState;

View File

@@ -1,12 +0,0 @@
import AppSectionState, {
AppSectionDeleteState,
AppSectionSaveState,
} from 'App/State/AppSectionState';
import RootFolder from 'typings/RootFolder';
interface RootFolderAppState
extends AppSectionState<RootFolder>,
AppSectionDeleteState,
AppSectionSaveState {}
export default RootFolderAppState;

View File

@@ -1,6 +1,5 @@
import AppSectionState, {
AppSectionDeleteState,
AppSectionItemState,
AppSectionSaveState,
AppSectionSchemaState,
} from 'App/State/AppSectionState';
@@ -8,7 +7,6 @@ import Language from 'Language/Language';
import DownloadClient from 'typings/DownloadClient';
import ImportList from 'typings/ImportList';
import Indexer from 'typings/Indexer';
import IndexerFlag from 'typings/IndexerFlag';
import Notification from 'typings/Notification';
import QualityProfile from 'typings/QualityProfile';
import { UiSettings } from 'typings/UiSettings';
@@ -36,19 +34,17 @@ export interface QualityProfilesAppState
extends AppSectionState<QualityProfile>,
AppSectionSchemaState<QualityProfile> {}
export type IndexerFlagSettingsAppState = AppSectionState<IndexerFlag>;
export type LanguageSettingsAppState = AppSectionState<Language>;
export type UiSettingsAppState = AppSectionItemState<UiSettings>;
export type UiSettingsAppState = AppSectionState<UiSettings>;
interface SettingsAppState {
downloadClients: DownloadClientAppState;
importLists: ImportListAppState;
indexerFlags: IndexerFlagSettingsAppState;
indexers: IndexerAppState;
languages: LanguageSettingsAppState;
notifications: NotificationAppState;
language: LanguageSettingsAppState;
uiSettings: UiSettingsAppState;
qualityProfiles: QualityProfilesAppState;
ui: UiSettingsAppState;
}
export default SettingsAppState;

View File

@@ -1,10 +0,0 @@
import SystemStatus from 'typings/SystemStatus';
import { AppSectionItemState } from './AppSectionState';
export type SystemStatusAppState = AppSectionItemState<SystemStatus>;
interface SystemAppState {
status: SystemStatusAppState;
}
export default SystemAppState;

View File

@@ -1,32 +1,12 @@
import ModelBase from 'App/ModelBase';
import AppSectionState, {
AppSectionDeleteState,
AppSectionSaveState,
} from 'App/State/AppSectionState';
export interface Tag extends ModelBase {
label: string;
}
export interface TagDetail extends ModelBase {
label: string;
autoTagIds: number[];
delayProfileIds: number[];
downloadClientIds: number[];
importListIds: number[];
indexerIds: number[];
movieIds: number[];
notificationIds: number[];
restrictionIds: number[];
}
export interface TagDetailAppState
extends AppSectionState<TagDetail>,
AppSectionDeleteState,
AppSectionSaveState {}
interface TagsAppState extends AppSectionState<Tag>, AppSectionDeleteState {
details: TagDetailAppState;
}
interface TagsAppState extends AppSectionState<Tag>, AppSectionDeleteState {}
export default TagsAppState;

View File

@@ -1,28 +1,17 @@
.event {
position: relative;
display: flex;
overflow-x: hidden;
padding: 5px;
border-bottom: 1px solid var(--borderColor);
}
.underlay {
@add-mixin cover;
font-size: $defaultFontSize;
&:hover {
background-color: var(--tableRowHoverBackgroundColor);
}
}
.overlay {
@add-mixin linkOverlay;
position: relative;
display: flex;
overflow-x: hidden;
font-size: $defaultFontSize;
&:global(.colorImpaired) {
border-left-width: 5px;
}
.link {
composes: link from '~Calendar/Events/CalendarEvent.css';
}
.eventWrapper {
@@ -55,8 +44,6 @@
.statusIcon {
margin-left: 3px;
cursor: default;
pointer-events: all;
}
/*
@@ -108,8 +95,6 @@
}
}
.releaseIcon {
margin-right: 20px;
.dateIcon {
width: 25px;
text-align: right;
}

View File

@@ -3,19 +3,18 @@
interface CssExports {
'continuing': string;
'date': string;
'dateIcon': string;
'downloaded': string;
'event': string;
'eventWrapper': string;
'genres': string;
'link': string;
'missingMonitored': string;
'missingUnmonitored': string;
'movieTitle': string;
'overlay': string;
'queue': string;
'releaseIcon': string;
'statusIcon': string;
'time': string;
'underlay': string;
'unmonitored': string;
}
export const cssExports: CssExports;

View File

@@ -87,24 +87,25 @@ class AgendaEvent extends Component {
const link = `/movie/${titleSlug}`;
return (
<div className={styles.event}>
<div>
<Link
className={styles.underlay}
className={classNames(
styles.event,
styles.link
)}
to={link}
/>
<div className={styles.overlay}>
<div className={styles.date}>
{(showDate) ? startTime.format(longDateFormat) : null}
</div>
<div className={styles.releaseIcon}>
>
<div className={styles.dateIcon}>
<Icon
name={releaseIcon}
kind={kinds.DEFAULT}
/>
</div>
<div className={styles.date}>
{(showDate) ? startTime.format(longDateFormat) : null}
</div>
<div
className={classNames(
styles.eventWrapper,
@@ -142,7 +143,9 @@ class AgendaEvent extends Component {
}
{
showCutoffUnmetIcon && !!movieFile && movieFile.qualityCutoffNotMet &&
showCutoffUnmetIcon &&
!!movieFile &&
movieFile.qualityCutoffNotMet &&
<Icon
className={styles.statusIcon}
name={icons.MOVIE_FILE}
@@ -151,7 +154,7 @@ class AgendaEvent extends Component {
/>
}
</div>
</div>
</Link>
</div>
);
}

View File

@@ -1,8 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import AgendaConnector from './Agenda/AgendaConnector';
import * as calendarViews from './calendarViews';
@@ -33,9 +31,9 @@ class Calendar extends Component {
{
!isFetching && !!error &&
<Alert kind={kinds.DANGER}>
<div>
{translate('UnableToLoadTheCalendar')}
</Alert>
</div>
}
{

View File

@@ -1,56 +0,0 @@
import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import FilterModal from 'Components/Filter/FilterModal';
import { setCalendarFilter } from 'Store/Actions/calendarActions';
function createCalendarSelector() {
return createSelector(
(state: AppState) => state.calendar.items,
(calendar) => {
return calendar;
}
);
}
function createFilterBuilderPropsSelector() {
return createSelector(
(state: AppState) => state.calendar.filterBuilderProps,
(filterBuilderProps) => {
return filterBuilderProps;
}
);
}
interface SeriesIndexFilterModalProps {
isOpen: boolean;
}
export default function CalendarFilterModal(
props: SeriesIndexFilterModalProps
) {
const sectionItems = useSelector(createCalendarSelector());
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
const customFilterType = 'calendar';
const dispatch = useDispatch();
const dispatchSetFilter = useCallback(
(payload: unknown) => {
dispatch(setCalendarFilter(payload));
},
[dispatch]
);
return (
<FilterModal
// TODO: Don't spread all the props
{...props}
sectionItems={sectionItems}
filterBuilderProps={filterBuilderProps}
customFilterType={customFilterType}
dispatchSetFilter={dispatchSetFilter}
/>
);
}

View File

@@ -14,7 +14,6 @@ import NoMovie from 'Movie/NoMovie';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate';
import CalendarConnector from './CalendarConnector';
import CalendarFilterModal from './CalendarFilterModal';
import CalendarLinkModal from './iCal/CalendarLinkModal';
import LegendConnector from './Legend/LegendConnector';
import CalendarOptionsModal from './Options/CalendarOptionsModal';
@@ -84,7 +83,6 @@ class CalendarPage extends Component {
movieIsFetching,
movieIsPopulated,
missingMovieIds,
customFilters,
isRssSyncExecuting,
isSearchingForMissing,
useCurrentPage,
@@ -139,8 +137,7 @@ class CalendarPage extends Component {
isDisabled={!hasMovie}
selectedFilterKey={selectedFilterKey}
filters={filters}
customFilters={customFilters}
filterModalConnectorComponent={CalendarFilterModal}
customFilters={[]}
onFilterSelect={onFilterSelect}
/>
</PageToolbarSection>
@@ -211,7 +208,6 @@ CalendarPage.propTypes = {
movieIsFetching: PropTypes.bool.isRequired,
movieIsPopulated: PropTypes.bool.isRequired,
missingMovieIds: PropTypes.arrayOf(PropTypes.number).isRequired,
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
isRssSyncExecuting: PropTypes.bool.isRequired,
isSearchingForMissing: PropTypes.bool.isRequired,
useCurrentPage: PropTypes.bool.isRequired,

View File

@@ -5,7 +5,6 @@ import * as commandNames from 'Commands/commandNames';
import withCurrentPage from 'Components/withCurrentPage';
import { searchMissing, setCalendarDaysCount, setCalendarFilter } from 'Store/Actions/calendarActions';
import { executeCommand } from 'Store/Actions/commandActions';
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
import createMovieCountSelector from 'Store/Selectors/createMovieCountSelector';
@@ -60,7 +59,6 @@ function createMapStateToProps() {
return createSelector(
(state) => state.calendar.selectedFilterKey,
(state) => state.calendar.filters,
createCustomFiltersSelector('calendar'),
createMovieCountSelector(),
createUISettingsSelector(),
createMissingMovieIdsSelector(),
@@ -69,7 +67,6 @@ function createMapStateToProps() {
(
selectedFilterKey,
filters,
customFilters,
movieCount,
uiSettings,
missingMovieIds,
@@ -79,7 +76,6 @@ function createMapStateToProps() {
return {
selectedFilterKey,
filters,
customFilters,
colorImpairedMode: uiSettings.enableColorImpairedMode,
hasMovie: !!movieCount.count,
movieError: movieCount.error,

View File

@@ -0,0 +1,64 @@
import classNames from 'classnames';
import moment from 'moment';
import PropTypes from 'prop-types';
import React from 'react';
import * as calendarViews from 'Calendar/calendarViews';
import CalendarEventConnector from 'Calendar/Events/CalendarEventConnector';
import styles from './CalendarDay.css';
function CalendarDay(props) {
const {
date,
time,
isTodaysDate,
events,
view,
onEventModalOpenToggle
} = props;
return (
<div className={classNames(
styles.day,
view === calendarViews.DAY && styles.isSingleDay
)}
>
{
view === calendarViews.MONTH &&
<div className={classNames(
styles.dayOfMonth,
isTodaysDate && styles.isToday,
!moment(date).isSame(moment(time), 'month') && styles.isDifferentMonth
)}
>
{moment(date).date()}
</div>
}
<div>
{
events.map((event) => {
return (
<CalendarEventConnector
key={event.id}
movieId={event.id}
date={date}
{...event}
onEventModalOpenToggle={onEventModalOpenToggle}
/>
);
})
}
</div>
</div>
);
}
CalendarDay.propTypes = {
date: PropTypes.string.isRequired,
time: PropTypes.string.isRequired,
isTodaysDate: PropTypes.bool.isRequired,
events: PropTypes.arrayOf(PropTypes.object).isRequired,
view: PropTypes.string.isRequired,
onEventModalOpenToggle: PropTypes.func.isRequired
};
export default CalendarDay;

View File

@@ -1,67 +0,0 @@
import classNames from 'classnames';
import moment from 'moment';
import React from 'react';
import * as calendarViews from 'Calendar/calendarViews';
import CalendarEventConnector from 'Calendar/Events/CalendarEventConnector';
import CalendarEvent from 'typings/CalendarEvent';
import styles from './CalendarDay.css';
interface CalendarDayProps {
date: string;
time: string;
isTodaysDate: boolean;
events: CalendarEvent[];
view: string;
onEventModalOpenToggle(...args: unknown[]): unknown;
}
function CalendarDay(props: CalendarDayProps) {
const { date, time, isTodaysDate, events, view, onEventModalOpenToggle } =
props;
const ref = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
if (isTodaysDate && view === calendarViews.MONTH && ref.current) {
ref.current.scrollIntoView();
}
}, [time, isTodaysDate, view]);
return (
<div
ref={ref}
className={classNames(
styles.day,
view === calendarViews.DAY && styles.isSingleDay
)}
>
{view === calendarViews.MONTH && (
<div
className={classNames(
styles.dayOfMonth,
isTodaysDate && styles.isToday,
!moment(date).isSame(moment(time), 'month') &&
styles.isDifferentMonth
)}
>
{moment(date).date()}
</div>
)}
<div>
{events.map((event) => {
return (
<CalendarEventConnector
key={event.id}
{...event}
movieId={event.id}
date={date as string}
onEventModalOpenToggle={onEventModalOpenToggle}
/>
);
})}
</div>
</div>
);
}
export default CalendarDay;

View File

@@ -1,22 +1,9 @@
$fullColorGradient: rgba(244, 245, 246, 0.2);
.event {
position: relative;
overflow-x: hidden;
margin: 4px 2px;
padding: 5px;
border-bottom: 1px solid var(--calendarBorderColor);
border-left: 4px solid var(--calendarBorderColor);
}
.underlay {
@add-mixin cover;
}
.overlay {
@add-mixin linkOverlay;
position: relative;
overflow-x: hidden;
font-size: 12px;
&:global(.colorImpaired) {
@@ -24,6 +11,18 @@ $fullColorGradient: rgba(244, 245, 246, 0.2);
}
}
.link {
composes: link from '~Components/Link/Link.css';
display: block;
color: var(--defaultColor);
&:hover {
color: var(--defaultColor);
text-decoration: none;
}
}
.info,
.movieInfo {
display: flex;
@@ -45,15 +44,8 @@ $fullColorGradient: rgba(244, 245, 246, 0.2);
font-size: $defaultFontSize;
}
.statusContainer {
display: flex;
align-items: center;
}
.statusIcon {
margin-left: 3px;
cursor: default;
pointer-events: all;
}
/*
@@ -63,84 +55,35 @@ $fullColorGradient: rgba(244, 245, 246, 0.2);
.downloaded {
border-left-color: var(--successColor) !important;
&:global(.fullColor) {
background-color: rgba(39, 194, 76, 0.4) !important;
}
&:global(.colorImpaired) {
border-left-color: color(#27c24c saturation(+15%)) !important;
border-left-color: color(var(--successColor), saturation(+15%)) !important;
}
}
.queue {
border-left-color: var(--purple) !important;
&:global(.fullColor) {
background-color: rgba(122, 67, 182, 0.4) !important;
}
}
.unmonitored {
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 {
border-left-color: var(--warningColor) !important;
&:global(.fullColor) {
background-color: rgba(255, 165, 0, 0.6) !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);
background: repeating-linear-gradient(45deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px);
}
}
.missingMonitored {
border-left-color: var(--dangerColor) !important;
&:global(.fullColor) {
background-color: rgba(240, 80, 80, 0.6) !important;
}
&:global(.colorImpaired) {
border-left-color: color(#f05050 saturation(+15%)) !important;
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 {
border-left-color: var(--primaryColor) !important;
&:global(.fullColor) {
background-color: rgba(93, 156, 236, 0.4) !important;
}
&:global(.colorImpaired) {
background: repeating-linear-gradient(90deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px);
}
&:global(.fullColor.colorImpaired) {
background: repeating-linear-gradient(90deg, $fullColorGradient, $fullColorGradient 5px, transparent 5px, transparent 10px);
}
}

View File

@@ -6,15 +6,13 @@ interface CssExports {
'event': string;
'genres': string;
'info': string;
'link': string;
'missingMonitored': string;
'missingUnmonitored': string;
'movieInfo': string;
'movieTitle': string;
'overlay': string;
'queue': string;
'statusContainer': string;
'statusIcon': string;
'underlay': string;
'unmonitored': string;
}
export const cssExports: CssExports;

View File

@@ -25,7 +25,6 @@ class CalendarEvent extends Component {
title,
titleSlug,
genres,
date,
monitored,
certification,
hasFile,
@@ -33,8 +32,8 @@ class CalendarEvent extends Component {
queueItem,
showMovieInformation,
showCutoffUnmetIcon,
fullColorEvents,
colorImpairedMode
colorImpairedMode,
date
} = this.props;
const isDownloading = !!(queueItem || grabbed);
@@ -57,71 +56,64 @@ class CalendarEvent extends Component {
}
return (
<div
className={classNames(
styles.event,
styles[statusStyle],
colorImpairedMode && 'colorImpaired',
fullColorEvents && 'fullColor'
)}
>
<div>
<Link
className={styles.underlay}
className={classNames(
styles.event,
styles.link,
styles[statusStyle],
colorImpairedMode && 'colorImpaired'
)}
// component="div"
to={link}
/>
<div className={styles.overlay} >
>
<div className={styles.info}>
<div className={styles.movieTitle}>
{title}
</div>
<div className={styles.statusContainer}>
{
queueItem ?
<span className={styles.statusIcon}>
<CalendarEventQueueDetails
{...queueItem}
/>
</span> :
null
}
{
!!queueItem &&
<span className={styles.statusIcon}>
<CalendarEventQueueDetails
{...queueItem}
/>
</span>
}
{
!queueItem && grabbed ?
<Icon
className={styles.statusIcon}
name={icons.DOWNLOADING}
title={translate('MovieIsDownloading')}
/> :
null
}
{
!queueItem && grabbed &&
<Icon
className={styles.statusIcon}
name={icons.DOWNLOADING}
title={translate('MovieIsDownloading')}
/>
}
{
showCutoffUnmetIcon && !!movieFile && movieFile.qualityCutoffNotMet ?
<Icon
className={styles.statusIcon}
name={icons.MOVIE_FILE}
kind={kinds.WARNING}
title={translate('QualityCutoffHasNotBeenMet')}
/> :
null
}
</div>
{
showCutoffUnmetIcon &&
!!movieFile &&
movieFile.qualityCutoffNotMet &&
<Icon
className={styles.statusIcon}
name={icons.MOVIE_FILE}
kind={kinds.WARNING}
title={translate('QualityCutoffHasNotBeenMet')}
/>
}
</div>
{
showMovieInformation ?
showMovieInformation &&
<div className={styles.movieInfo}>
<div className={styles.genres}>
{joinedGenres}
</div>
</div> :
null
</div>
}
{
showMovieInformation ?
showMovieInformation &&
<div className={styles.movieInfo}>
<div className={styles.genres}>
{eventType.join(', ')}
@@ -129,10 +121,10 @@ class CalendarEvent extends Component {
<div>
{certification}
</div>
</div> :
null
</div>
}
</div>
</Link>
</div>
);
}
@@ -148,18 +140,16 @@ CalendarEvent.propTypes = {
inCinemas: PropTypes.string,
physicalRelease: PropTypes.string,
digitalRelease: PropTypes.string,
date: PropTypes.string.isRequired,
monitored: PropTypes.bool.isRequired,
certification: PropTypes.string,
hasFile: PropTypes.bool.isRequired,
grabbed: PropTypes.bool,
queueItem: PropTypes.object,
// These props come from the connector, not marked as required to appease TS for now.
showMovieInformation: PropTypes.bool,
showCutoffUnmetIcon: PropTypes.bool,
fullColorEvents: PropTypes.bool,
timeFormat: PropTypes.string,
colorImpairedMode: PropTypes.bool
showMovieInformation: PropTypes.bool.isRequired,
showCutoffUnmetIcon: PropTypes.bool.isRequired,
timeFormat: PropTypes.string.isRequired,
colorImpairedMode: PropTypes.bool.isRequired,
date: PropTypes.string.isRequired
};
CalendarEvent.defaultProps = {

View File

@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import QueueDetails from 'Activity/Queue/QueueDetails';
import CircularProgressBar from 'Components/CircularProgressBar';
import translate from 'Utilities/String/translate';
function CalendarEventQueueDetails(props) {
const {
@@ -12,7 +13,6 @@ function CalendarEventQueueDetails(props) {
status,
trackedDownloadState,
trackedDownloadStatus,
statusMessages,
errorMessage
} = props;
@@ -27,15 +27,16 @@ function CalendarEventQueueDetails(props) {
status={status}
trackedDownloadState={trackedDownloadState}
trackedDownloadStatus={trackedDownloadStatus}
statusMessages={statusMessages}
errorMessage={errorMessage}
progressBar={
<CircularProgressBar
progress={progress}
size={20}
strokeWidth={2}
strokeColor={'#7a43b6'}
/>
<div title={translate('MovieIsDownloadingInterp', [progress.toFixed(1), title])}>
<CircularProgressBar
progress={progress}
size={20}
strokeWidth={2}
strokeColor={'#7a43b6'}
/>
</div>
}
/>
);
@@ -49,7 +50,6 @@ CalendarEventQueueDetails.propTypes = {
status: PropTypes.string.isRequired,
trackedDownloadState: PropTypes.string.isRequired,
trackedDownloadStatus: PropTypes.string.isRequired,
statusMessages: PropTypes.arrayOf(PropTypes.object),
errorMessage: PropTypes.string
};

View File

@@ -8,21 +8,18 @@ import styles from './Legend.css';
function Legend(props) {
const {
view,
showCutoffUnmetIcon,
fullColorEvents,
colorImpairedMode
} = props;
const iconsToShow = [];
const isAgendaView = view === 'agenda';
if (showCutoffUnmetIcon) {
iconsToShow.push(
<LegendIconItem
name={translate('CutoffUnmet')}
icon={icons.MOVIE_FILE}
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
kind={kinds.WARNING}
tooltip={translate('QualityOrLangCutoffHasNotBeenMet')}
/>
);
@@ -32,58 +29,45 @@ function Legend(props) {
<div className={styles.legend}>
<div>
<LegendItem
status="downloaded"
style='ended'
name={translate('DownloadedAndMonitored')}
isAgendaView={isAgendaView}
fullColorEvents={fullColorEvents}
colorImpairedMode={colorImpairedMode}
/>
<LegendItem
status="unmonitored"
style='availNotMonitored'
name={translate('DownloadedButNotMonitored')}
isAgendaView={isAgendaView}
fullColorEvents={fullColorEvents}
colorImpairedMode={colorImpairedMode}
/>
</div>
<div>
<LegendItem
status="missingMonitored"
style='missingMonitored'
name={translate('MissingMonitoredAndConsideredAvailable')}
isAgendaView={isAgendaView}
fullColorEvents={fullColorEvents}
colorImpairedMode={colorImpairedMode}
/>
<LegendItem
status="missingUnmonitored"
style='missingUnmonitored'
name={translate('MissingNotMonitored')}
isAgendaView={isAgendaView}
fullColorEvents={fullColorEvents}
colorImpairedMode={colorImpairedMode}
/>
</div>
<div>
<LegendItem
status="queue"
style='queue'
name={translate('Queued')}
isAgendaView={isAgendaView}
fullColorEvents={fullColorEvents}
colorImpairedMode={colorImpairedMode}
/>
<LegendItem
status="continuing"
style='continuing'
name={translate('Unreleased')}
isAgendaView={isAgendaView}
fullColorEvents={fullColorEvents}
colorImpairedMode={colorImpairedMode}
/>
</div>
{
iconsToShow.length > 0 &&
<div>
@@ -95,9 +79,7 @@ function Legend(props) {
}
Legend.propTypes = {
view: PropTypes.string.isRequired,
showCutoffUnmetIcon: PropTypes.bool.isRequired,
fullColorEvents: PropTypes.bool.isRequired,
colorImpairedMode: PropTypes.bool.isRequired
};

View File

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

View File

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

View File

@@ -1,37 +1,74 @@
.legendItem {
margin: 3px 0;
margin-right: 6px;
padding-left: 5px;
.legendItemContainer {
margin-right: 5px;
width: 220px;
border-left-width: 4px;
border-left-style: solid;
cursor: default;
}
/*
* Status
*/
.legendItem {
display: inline-flex;
margin-top: -1px;
vertical-align: middle;
line-height: 16px;
}
.downloaded {
composes: downloaded from '~Calendar/Events/CalendarEvent.css';
.legendItemColor {
margin-right: 8px;
width: 30px;
height: 16px;
border-radius: 4px;
}
.queue {
composes: queue from '~Calendar/Events/CalendarEvent.css';
}
composes: legendItemColor;
.unmonitored {
composes: unmonitored from '~Calendar/Events/CalendarEvent.css';
}
.missingUnmonitored {
composes: missingUnmonitored from '~Calendar/Events/CalendarEvent.css';
}
.missingMonitored {
composes: missingMonitored from '~Calendar/Events/CalendarEvent.css';
background-color: var(--queueColor);
}
.continuing {
composes: continuing from '~Calendar/Events/CalendarEvent.css';
composes: legendItemColor;
background-color: var(--primaryColor);
}
.availNotMonitored {
composes: legendItemColor;
background-color: var(--darkGray);
}
.ended {
composes: legendItemColor;
background-color: var(--successColor);
}
.missingMonitored {
composes: legendItemColor;
background-color: var(--dangerColor);
&:global(.colorImpaired) {
background: repeating-linear-gradient(90deg, color(var(--dangerColor) shade(5%)), color(var(--dangerColor) shade(5%)) 5px, color(var(--dangerColor) shade(15%)) 5px, color(var(--dangerColor) shade(15%)) 10px);
}
}
.missingUnmonitored {
composes: legendItemColor;
background-color: var(--warningColor);
&:global(.colorImpaired) {
background: repeating-linear-gradient(45deg, var(--warningColor), var(--warningColor) 5px, color(var(--warningColor) tint(15%)) 5px, color(var(--warningColor) tint(15%)) 10px);
}
}
.missingMonitoredColorImpaired {
background: repeating-linear-gradient(90deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px);
}
.missingUnmonitoredColorImpaired {
background: repeating-linear-gradient(45deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px);
}
.legendItemText {
display: inline-block;
}

View File

@@ -1,13 +1,18 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'availNotMonitored': string;
'continuing': string;
'downloaded': string;
'ended': string;
'legendItem': string;
'legendItemColor': string;
'legendItemContainer': string;
'legendItemText': string;
'missingMonitored': string;
'missingMonitoredColorImpaired': string;
'missingUnmonitored': string;
'missingUnmonitoredColorImpaired': string;
'queue': string;
'unmonitored': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@@ -6,31 +6,29 @@ import styles from './LegendItem.css';
function LegendItem(props) {
const {
name,
status,
isAgendaView,
fullColorEvents,
style,
colorImpairedMode
} = props;
return (
<div
className={classNames(
styles.legendItem,
styles[status],
colorImpairedMode && 'colorImpaired',
fullColorEvents && !isAgendaView && 'fullColor'
)}
>
{name}
<div className={styles.legendItemContainer}>
<div
className={classNames(
styles.legendItem,
styles[style],
colorImpairedMode && 'colorImpaired'
)}
/>
<div className={classNames(styles.legendItemText, colorImpairedMode && styles[`${style}ColorImpaired`])}>
{name}
</div>
</div>
);
}
LegendItem.propTypes = {
name: PropTypes.string.isRequired,
status: PropTypes.string.isRequired,
isAgendaView: PropTypes.bool.isRequired,
fullColorEvents: PropTypes.bool.isRequired,
style: PropTypes.string.isRequired,
colorImpairedMode: PropTypes.bool.isRequired
};

View File

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

View File

@@ -28,7 +28,7 @@ function createMapStateToProps() {
qualityProfileId: collection.qualityProfileId,
minimumAvailability: collection.minimumAvailability,
searchForMovie: collection.searchOnAdd,
tags: collection.tags || []
tags: []
};
const {

View File

@@ -1,7 +1,6 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
@@ -10,7 +9,7 @@ import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import { align, icons, kinds, sortDirections } from 'Helpers/Props';
import { align, icons, sortDirections } from 'Helpers/Props';
import styles from 'Movie/Index/MovieIndex.css';
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
import translate from 'Utilities/String/translate';
@@ -314,9 +313,9 @@ class Collection extends Component {
{
!isFetching && !!error &&
<Alert kind={kinds.DANGER}>
<div>
{translate('UnableToLoadCollections')}
</Alert>
</div>
}
{

View File

@@ -2,12 +2,17 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createAllMoviesSelector from 'Store/Selectors/createAllMoviesSelector';
import createCollectionSelector from 'Store/Selectors/createCollectionSelector';
function createMapStateToProps() {
return createSelector(
createCollectionSelector(),
(collection) => {
createAllMoviesSelector(),
(
collection,
allMovies
) => {
// If a movie is deleted this selector may fire before the parent
// selecors, which will result in an undefined movie, if that happens
// we want to return early here and again in the render function to avoid
@@ -17,11 +22,21 @@ function createMapStateToProps() {
return {};
}
const allGenres = collection.movies.flatMap((movie) => movie.genres);
let allGenres = [];
let libraryMovies = 0;
collection.movies.forEach((movie) => {
allGenres = allGenres.concat(movie.genres);
if (allMovies.find((libraryMovie) => libraryMovie.tmdbId === movie.tmdbId)) {
libraryMovies++;
}
});
return {
...collection,
genres: Array.from(new Set(allGenres)).slice(0, 3)
genres: Array.from(new Set(allGenres)).slice(0, 3),
missingMovies: collection.movies.length - libraryMovies
};
}
);

View File

@@ -50,7 +50,6 @@ class EditCollectionModalContent extends Component {
minimumAvailability,
// Id,
rootFolderPath,
tags,
searchOnAdd
} = item;
@@ -127,17 +126,6 @@ class EditCollectionModalContent extends Component {
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('Tags')}</FormLabel>
<FormInputGroup
type={inputTypes.TAG}
name="tags"
onChange={onInputChange}
{...tags}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('SearchOnAdd')}</FormLabel>

View File

@@ -42,7 +42,6 @@ function createMapStateToProps() {
qualityProfileId: collection.qualityProfileId,
minimumAvailability: collection.minimumAvailability,
rootFolderPath: collection.rootFolderPath,
tags: collection.tags,
searchOnAdd: collection.searchOnAdd
};

View File

@@ -28,14 +28,6 @@ function CollectionSortMenu(props) {
>
{translate('Title')}
</SortMenuItem>
<SortMenuItem
name="missingMovies"
sortKey={sortKey}
sortDirection={sortDirection}
onPress={onSortSelect}
>
{translate('Missing')}
</SortMenuItem>
</MenuContent>
</SortMenu>
);

View File

@@ -102,7 +102,7 @@ $hoverScale: 1.05;
position: relative;
display: block;
background-color: var(--black);
background-color: var(--defaultColor);
}
.monitorToggleButton {

View File

@@ -258,7 +258,7 @@ CollectionOverviews.propTypes = {
sortKey: PropTypes.string,
overviewOptions: PropTypes.object.isRequired,
jumpToCharacter: PropTypes.string,
scrollTop: PropTypes.number,
scrollTop: PropTypes.number.isRequired,
scroller: PropTypes.instanceOf(Element).isRequired,
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,

View File

@@ -14,24 +14,9 @@ import { inputTypes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
const posterSizeOptions = [
{
key: 'small',
get value() {
return translate('Small');
}
},
{
key: 'medium',
get value() {
return translate('Medium');
}
},
{
key: 'large',
get value() {
return translate('Large');
}
}
{ key: 'small', value: translate('Small') },
{ key: 'medium', value: translate('Medium') },
{ key: 'large', value: translate('Large') }
];
class CollectionOverviewOptionsModalContent extends Component {

View File

@@ -10,7 +10,6 @@ class DescriptionListItem extends Component {
render() {
const {
className,
titleClassName,
descriptionClassName,
title,
@@ -18,7 +17,7 @@ class DescriptionListItem extends Component {
} = this.props;
return (
<div className={className}>
<div>
<DescriptionListItemTitle
className={titleClassName}
>
@@ -36,7 +35,6 @@ class DescriptionListItem extends Component {
}
DescriptionListItem.propTypes = {
className: PropTypes.string,
titleClassName: PropTypes.string,
descriptionClassName: PropTypes.string,
title: PropTypes.string,

View File

@@ -20,12 +20,12 @@ import styles from './FileBrowserModalContent.css';
const columns = [
{
name: 'type',
label: () => translate('Type'),
label: translate('Type'),
isVisible: true
},
{
name: 'name',
label: () => translate('Name'),
label: translate('Name'),
isVisible: true
}
];

View File

@@ -10,42 +10,12 @@ import { NAME } from './FilterBuilderRowValue';
import styles from './DateFilterBuilderRowValue.css';
const timeOptions = [
{
key: 'seconds',
get value() {
return translate('Seconds');
}
},
{
key: 'minutes',
get value() {
return translate('Minutes');
}
},
{
key: 'hours',
get value() {
return translate('Hours');
}
},
{
key: 'days',
get value() {
return translate('Days');
}
},
{
key: 'weeks',
get value() {
return translate('Weeks');
}
},
{
key: 'months',
get value() {
return translate('Months');
}
}
{ key: 'seconds', value: translate('Seconds') },
{ key: 'minutes', value: translate('Minutes') },
{ key: 'hours', value: translate('Hours') },
{ key: 'days', value: translate('Days') },
{ key: 'weeks', value: translate('Weeks') },
{ key: 'months', value: translate('Months') }
];
function isInFilter(filterType) {

View File

@@ -210,13 +210,11 @@ class FilterBuilderRow extends Component {
const selectedFilterBuilderProp = this.selectedFilterBuilderProp;
const keyOptions = filterBuilderProps.map((availablePropFilter) => {
const { name, label } = availablePropFilter;
return {
key: name,
value: typeof label === 'function' ? label() : label
key: availablePropFilter.name,
value: availablePropFilter.label
};
}).sort((a, b) => a.value.localeCompare(b.value));
});
const ValueComponent = getRowValueConnector(selectedFilterBuilderProp);

View File

@@ -2,7 +2,6 @@ import PropTypes from 'prop-types';
import React from 'react';
import TagInputTag from 'Components/Form/TagInputTag';
import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './FilterBuilderRowValueTag.css';
function FilterBuilderRowValueTag(props) {
@@ -16,11 +15,10 @@ function FilterBuilderRowValueTag(props) {
/>
{
props.isLastTag ?
null :
<div className={styles.or}>
{translate('Or')}
</div>
!props.isLastTag &&
<span className={styles.or}>
or
</span>
}
</span>
);

View File

@@ -3,24 +3,9 @@ import translate from 'Utilities/String/translate';
import FilterBuilderRowValue from './FilterBuilderRowValue';
const protocols = [
{
id: 'announced',
get name() {
return translate('Announced');
}
},
{
id: 'inCinemas',
get name() {
return translate('InCinemas');
}
},
{
id: 'released',
get name() {
return translate('Released');
}
}
{ id: 'announced', name: translate('Announced') },
{ id: 'inCinemas', name: translate('InCinemas') },
{ id: 'released', name: translate('Released') }
];
function MinimumAvailabilityFilterBuilderRowValue(props) {

View File

@@ -4,30 +4,10 @@ import FilterBuilderRowValue from './FilterBuilderRowValue';
const protocols = [
{ id: 'tba', name: 'TBA' },
{
id: 'announced',
get name() {
return translate('Announced');
}
},
{
id: 'inCinemas',
get name() {
return translate('InCinemas');
}
},
{
id: 'released',
get name() {
return translate('Released');
}
},
{
id: 'deleted',
get name() {
return translate('Deleted');
}
}
{ id: 'announced', name: translate('Announced') },
{ id: 'inCinemas', name: translate('InCinemas') },
{ id: 'released', name: translate('Released') },
{ id: 'deleted', name: translate('Deleted') }
];
function ReleaseStatusFilterBuilderRowValue(props) {

View File

@@ -4,24 +4,9 @@ import translate from 'Utilities/String/translate';
import EnhancedSelectInput from './EnhancedSelectInput';
const availabilityOptions = [
{
key: 'announced',
get value() {
return translate('Announced');
}
},
{
key: 'inCinemas',
get value() {
return translate('InCinemas');
}
},
{
key: 'released',
get value() {
return translate('Released');
}
}
{ key: 'announced', value: translate('Announced') },
{ key: 'inCinemas', value: translate('InCinemas') },
{ key: 'released', value: translate('Released') }
];
function AvailabilitySelectInput(props) {
@@ -35,7 +20,7 @@ function AvailabilitySelectInput(props) {
if (includeNoChange) {
values.unshift({
key: 'noChange',
value: translate('NoChange'),
value: 'No Change',
disabled: true
});
}

View File

@@ -580,7 +580,7 @@ EnhancedSelectInput.propTypes = {
className: PropTypes.string,
disabledClassName: PropTypes.string,
name: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.arrayOf(PropTypes.string), PropTypes.arrayOf(PropTypes.number)]).isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.arrayOf(PropTypes.number)]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
isDisabled: PropTypes.bool.isRequired,
isFetching: PropTypes.bool.isRequired,

View File

@@ -9,10 +9,6 @@
&:hover {
background-color: var(--inputHoverBackgroundColor);
}
&.isDisabled {
cursor: not-allowed;
}
}
.optionCheck {

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