Compare commits
387 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 33d1d1f875 | |||
| fb60dcb5bf | |||
| ddf23530fc | |||
| 30b1edbff0 | |||
| f20c260a4f | |||
| 2fcbac49c7 | |||
| 3248e7f476 | |||
| ce145a3050 | |||
| 3bc4197b4a | |||
| 552b8f91d2 | |||
| e9e36ae56a | |||
| 450d6c0c80 | |||
| 9eece2965a | |||
| cd5d4f993a | |||
| fe7203815d | |||
| 4e01fa57fd | |||
| bbeb4d7b5f | |||
| 49dac0ebaa | |||
| ea8f5c7b9f | |||
| 24a17a9240 | |||
| 97c2d4f9db | |||
| b7cafb2917 | |||
| 2a2667a2ec | |||
| 27da524391 | |||
| 4bd1c14db9 | |||
| 608e2e7307 | |||
| cff54d76b9 | |||
| 3244282a83 | |||
| 1d488df242 | |||
| 22927224c6 | |||
| 51149bccdd | |||
| 4bbc166040 | |||
| 11c7446cbe | |||
| dce637905a | |||
| 7d85922f8d | |||
| 80f6033595 | |||
| 78b8747b50 | |||
| c2df194d49 | |||
| 4a41c67dfe | |||
| 85d51e485a | |||
| 50e2e9edef | |||
| 703c251b5c | |||
| a798556d32 | |||
| 69253a4ac4 | |||
| 4e827e726f | |||
| e3abda9afc | |||
| 0386ea9b71 | |||
| f0fcd23248 | |||
| 18f22d7ada | |||
| 1c4b5f2abf | |||
| b48b970f25 | |||
| e715557a0d | |||
| 248ac9619c | |||
| feff609685 | |||
| 07cfbb59da | |||
| 9db0058114 | |||
| 8d7f6b9de8 | |||
| 28c566a071 | |||
| e5963c9ee1 | |||
| 336cb4a2bc | |||
| ff3d38a515 | |||
| a2bde5e016 | |||
| cb04ef960e | |||
| ba732847ef | |||
| 1865257544 | |||
| 58e0b19d06 | |||
| 05c5bcbe15 | |||
| d6749a0c8e | |||
| 72fe25d7b2 | |||
| 0598d46ee8 | |||
| e73d05c0fe | |||
| 9a0ca650a3 | |||
| ac6da13a82 | |||
| 61ffc50b7f | |||
| fcd8a4a873 | |||
| 093bb94e42 | |||
| 14b9dd77af | |||
| d6b62e738a | |||
| 53254f6aeb | |||
| 756384d94a | |||
| 7f172dcfd1 | |||
| 02998cd59a | |||
| 37aa739611 | |||
| 0e2c98827f | |||
| 27f45b8fd6 | |||
| 2210ce9394 | |||
| bbef1590a3 | |||
| feb3131ad4 | |||
| 89f5595e64 | |||
| cf9cff61b5 | |||
| 6de0feda65 | |||
| 0f699a01f7 | |||
| be20a9d116 | |||
| 4c2fcef742 | |||
| 15a4c3b742 | |||
| 7b4f908f6d | |||
| 1b4dd405be | |||
| 135de2cad4 | |||
| 174ea347a8 | |||
| 9b4f80535e | |||
| 07b69e665d | |||
| 99441dfa67 | |||
| 8e80c85f03 | |||
| 429217d1d4 | |||
| 8257e01995 | |||
| bd3fad9636 | |||
| 3cbdba51e9 | |||
| c70ce92ee9 | |||
| c1a3a8249b | |||
| 0f93e04186 | |||
| fef666831f | |||
| 681a36e34f | |||
| 726b71027e | |||
| a8feef7e88 | |||
| 70b725a2dc | |||
| 4b3bd86e0f | |||
| 3878196f39 | |||
| a39cafe404 | |||
| d9e337f2fb | |||
| 3412e4139e | |||
| b7bacf785c | |||
| c6e3f3c26c | |||
| e4c5fc5c6e | |||
| 3c42ad0f7f | |||
| 5236d46c2b | |||
| 6f54a9e452 | |||
| 4b9107465c | |||
| 329e43c331 | |||
| f05f25af0c | |||
| e50abd276e | |||
| 933d9e074c | |||
| 993e4ca298 | |||
| 58eb24ff89 | |||
| 9516729385 | |||
| d626f0487d | |||
| 1350ccb236 | |||
| 63d05a6e78 | |||
| f60b27355b | |||
| abd63ea2a4 | |||
| 655f49b8c9 | |||
| d8c1fe5486 | |||
| 8afe4e8979 | |||
| 1935abbde2 | |||
| fdc6c66f7a | |||
| def127b93f | |||
| c75d398f14 | |||
| d4fada9b4e | |||
| 111c081545 | |||
| 7f3e7b360b | |||
| 329e37774f | |||
| 4a4037323e | |||
| 2d72c1ef34 | |||
| 337d01e4ed | |||
| 927ae86e44 | |||
| fefdd71b6d | |||
| 328850627a | |||
| f412228383 | |||
| dc82d0b6dd | |||
| 0e83c42f3a | |||
| fa80e8b7a2 | |||
| c03453f6f7 | |||
| 3ffb36a2df | |||
| 0a04fad85b | |||
| 3c7f7f2e03 | |||
| 32ec9d4872 | |||
| c8e04f0c35 | |||
| d6f849ac95 | |||
| fcea483612 | |||
| bcd87a3a30 | |||
| e3bcc3da3f | |||
| 056c2b5233 | |||
| a946546793 | |||
| f9f44aec7a | |||
| 99ff6aa9c4 | |||
| ca93a72d63 | |||
| 0c6eae256b | |||
| 508a15e09a | |||
| 180dafe696 | |||
| e3160466e0 | |||
| 9ccefe0095 | |||
| 104aadfdb7 | |||
| 8911386ed0 | |||
| 1e6540a419 | |||
| 693f8dc391 | |||
| 576e1e76af | |||
| 1f8877d192 | |||
| 8c93123126 | |||
| dd614ac005 | |||
| 82de5d6f9a | |||
| e8e54fdf99 | |||
| c3b856401e | |||
| 25f6f3ec6d | |||
| d28eb47a1a | |||
| 431bc14e76 | |||
| efe5c3beb7 | |||
| d61ce6112b | |||
| 531e948687 | |||
| 7ad4411e4d | |||
| e8e23e41dc | |||
| 0c1fc49d69 | |||
| 83632f91e6 | |||
| 1bbd08a5a0 | |||
| 298077940e | |||
| 4fb632e4fc | |||
| 7bcb492572 | |||
| a673535417 | |||
| e0d70dc341 | |||
| aa98b2bac9 | |||
| 145f67d14b | |||
| caea810908 | |||
| 9a567b93d0 | |||
| 6ecd41bc5a | |||
| d5b4f0efa9 | |||
| b337f62a34 | |||
| c42fc6094d | |||
| a6f61b2722 | |||
| 54bb267e17 | |||
| 00e2933052 | |||
| b8f06eb97d | |||
| bd49a4ee8b | |||
| 247ca9b22a | |||
| 779b65fa2e | |||
| 002cbdb864 | |||
| e36715d359 | |||
| 69b621b13a | |||
| 385c7971bb | |||
| 1129d3901c | |||
| d057d15ac7 | |||
| 722c20a5dc | |||
| 43a0e75acf | |||
| abad6a9f18 | |||
| 835a539275 | |||
| cd2d81a5aa | |||
| 5aee804bc0 | |||
| 12fcd3f9b9 | |||
| 47360d4d38 | |||
| 788782d009 | |||
| 847d6244aa | |||
| 8fd8128641 | |||
| 136075d233 | |||
| 02cec5312c | |||
| e5f728352c | |||
| 2cc1333e5c | |||
| a79980aae5 | |||
| f2bbef75dd | |||
| d5c1f58839 | |||
| 430ea81937 | |||
| 80099dcacb | |||
| 938b69b240 | |||
| 9839b482b2 | |||
| 4dbd962fca | |||
| 856c4fa4bb | |||
| 45f5ce5f29 | |||
| 9d3e7f62ca | |||
| 594ed666e1 | |||
| 36338310df | |||
| ffde07e4d6 | |||
| 90a1e1dbb3 | |||
| 8b5f305462 | |||
| 2fe6847eb3 | |||
| bf0f681d46 | |||
| f9cb4c1abd | |||
| 1190bf791c | |||
| 53eb88d9a9 | |||
| ed5c063127 | |||
| e691253419 | |||
| 2959f72a10 | |||
| 78ae059f3d | |||
| 7226cab3d8 | |||
| 622162c5f6 | |||
| e612d8c485 | |||
| b20e15855c | |||
| 41e95ef98c | |||
| d8ba7f4d67 | |||
| 97a28fee3b | |||
| e6b782aa20 | |||
| 7aa72b30cb | |||
| 490a0cb2fb | |||
| fa50f60e6b | |||
| b5c9c996a5 | |||
| 9b69d3b051 | |||
| c8a1e49b7b | |||
| 0b300eee1c | |||
| ed107cadac | |||
| a2216f23ec | |||
| faaef80a80 | |||
| 5f70581a59 | |||
| 8369a77365 | |||
| 00f4176dad | |||
| 056c224431 | |||
| d84230d4cc | |||
| a6197ba70d | |||
| 31a16ab571 | |||
| 79c9225b00 | |||
| dd6be39063 | |||
| 668797b406 | |||
| 7bb9250877 | |||
| 6ad9ebb19e | |||
| f8cbca7958 | |||
| f65835b874 | |||
| 59ea905e06 | |||
| d8eda4d089 | |||
| e4eb8f63bb | |||
| d936591b66 | |||
| c61cca7952 | |||
| f38077aac7 | |||
| 3055ed5336 | |||
| 164625a0b2 | |||
| 09ca0a1c0a | |||
| bef881a9e2 | |||
| f7e36581e1 | |||
| 20a8f1cbe7 | |||
| 3da8396b7e | |||
| d61f914bd7 | |||
| 25837adfc7 | |||
| 5516d7e3cd | |||
| e2647deea3 | |||
| 8c34946134 | |||
| 4a66a832b3 | |||
| 2d18e4f89e | |||
| d6c1721f51 | |||
| 99709d6445 | |||
| 916d43d70d | |||
| bc004b3b5b | |||
| 7a222dcd9f | |||
| 48b9c1e8b9 | |||
| 7dde88387a | |||
| 0eddf76622 | |||
| f69a847d9a | |||
| 97ed820575 | |||
| 0ee94a4624 | |||
| 3b7914f63b | |||
| 0005fa57ac | |||
| bbde1dc7a6 | |||
| 1c99ce8876 | |||
| 7a5ae56a96 | |||
| ae8820178d | |||
| c214a6b67b | |||
| b3f6774820 | |||
| 8fd4e41c85 | |||
| 8984fd735b | |||
| 3321123043 | |||
| 23a13b5c23 | |||
| 304a07e23f | |||
| 1e0ec4aefb | |||
| 5c46c75ce7 | |||
| 1c26dd4aca | |||
| 6fae00f51c | |||
| 653ef0a501 | |||
| e606ff05a4 | |||
| dd3ac26604 | |||
| 122d0056ea | |||
| fe41aada06 | |||
| bd1844030d | |||
| 148ee5983d | |||
| 372d15ecf3 | |||
| b95431500d | |||
| 3da72f54ef | |||
| 7cfff20cad | |||
| d3895dec8f | |||
| 690bab3264 | |||
| 986128e100 | |||
| dda0885f91 | |||
| 7e218a886d | |||
| 77cde138dc | |||
| 239109e3dd | |||
| 4804eb0769 | |||
| 60a55dfdac | |||
| cd82865303 | |||
| 439adb4ac6 | |||
| fd0ff78791 | |||
| cbae355402 | |||
| fed98a648f | |||
| e1c5656cff | |||
| e3f88e1711 | |||
| 8fd267580a | |||
| 8974aa823b | |||
| 41492efd2e | |||
| d008768fff | |||
| cb21fe535d | |||
| 4cce2727e2 | |||
| b1ff82da37 | |||
| c5266152c5 | |||
| 783878be1e | |||
| 0cbfb4ca65 | |||
| c22c9400c2 | |||
| 0288c4b704 |
@@ -270,7 +270,7 @@ dotnet_diagnostic.CA5397.severity = suggestion
|
|||||||
|
|
||||||
dotnet_diagnostic.SYSLIB0006.severity = none
|
dotnet_diagnostic.SYSLIB0006.severity = none
|
||||||
|
|
||||||
[*.{js,html,js,hbs,less,css}]
|
[*.{js,html,hbs,less,css,ts,tsx}]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: Bug Report
|
name: Bug Report
|
||||||
description: 'Report a new bug, if you are not 100% certain this is a bug please go to our Reddit or Discord first'
|
description: 'Report a new bug, if you are not 100% certain this is a bug please go to our Discord first'
|
||||||
labels: ['Type: Bug', 'Status: Needs Triage']
|
labels: ['Type: Bug', 'Status: Needs Triage']
|
||||||
body:
|
body:
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
@@ -65,18 +65,18 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Trace Logs?
|
label: Trace Logs? **Not Optional**
|
||||||
description: |
|
description: |
|
||||||
Trace Logs (https://wiki.servarr.com/radarr/troubleshooting#logging-and-log-files)
|
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.
|
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!
|
Additionally, any additional info? Screenshots? References? Anything that will give us more context about the issue you are encountering!
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: Trace Logs have been provided as applicable. Reports may be closed if the required logs are not provided.
|
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 for all bug reports
|
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`
|
||||||
options:
|
options:
|
||||||
- label: I have followed the steps in the wiki link above and provided the required trace logs that are relevant and show this issue.
|
- 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.
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
@@ -3,6 +3,3 @@ contact_links:
|
|||||||
- name: Support via Discord
|
- name: Support via Discord
|
||||||
url: https://radarr.video/discord
|
url: https://radarr.video/discord
|
||||||
about: Chat with users and devs on support and setup related topics.
|
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.
|
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# 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).
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
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'
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
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
|
|
||||||
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 577 B After Width: | Height: | Size: 331 B |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 666 B |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 987 B |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 21 KiB |
@@ -35,7 +35,6 @@ Note that only one type of a given movie is supported. If you want both an 4k ve
|
|||||||
|
|
||||||
[](https://wiki.servarr.com/radarr)
|
[](https://wiki.servarr.com/radarr)
|
||||||
[](https://radarr.video/discord)
|
[](https://radarr.video/discord)
|
||||||
[](https://www.reddit.com/r/Radarr)
|
|
||||||
|
|
||||||
Note: GitHub Issues are for Bugs and Feature Requests Only
|
Note: GitHub Issues are for Bugs and Feature Requests Only
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ variables:
|
|||||||
testsFolder: './_tests'
|
testsFolder: './_tests'
|
||||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||||
majorVersion: '4.6.2'
|
majorVersion: '5.0.3'
|
||||||
minorVersion: $[counter('minorVersion', 2000)]
|
minorVersion: $[counter('minorVersion', 2000)]
|
||||||
radarrVersion: '$(majorVersion).$(minorVersion)'
|
radarrVersion: '$(majorVersion).$(minorVersion)'
|
||||||
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
||||||
sentryOrg: 'servarr'
|
sentryOrg: 'servarr'
|
||||||
sentryUrl: 'https://sentry.servarr.com'
|
sentryUrl: 'https://sentry.servarr.com'
|
||||||
dotnetVersion: '6.0.408'
|
dotnetVersion: '6.0.413'
|
||||||
nodeVersion: '16.X'
|
nodeVersion: '16.X'
|
||||||
innoVersion: '6.2.0'
|
innoVersion: '6.2.0'
|
||||||
windowsImage: 'windows-2022'
|
windowsImage: 'windows-2022'
|
||||||
@@ -27,6 +27,10 @@ trigger:
|
|||||||
include:
|
include:
|
||||||
- develop
|
- develop
|
||||||
- master
|
- master
|
||||||
|
paths:
|
||||||
|
exclude:
|
||||||
|
- .github
|
||||||
|
- src/Radarr.Api.*/openapi.json
|
||||||
|
|
||||||
pr:
|
pr:
|
||||||
branches:
|
branches:
|
||||||
@@ -34,6 +38,7 @@ pr:
|
|||||||
- develop
|
- develop
|
||||||
paths:
|
paths:
|
||||||
exclude:
|
exclude:
|
||||||
|
- .github
|
||||||
- src/NzbDrone.Core/Localization/Core
|
- src/NzbDrone.Core/Localization/Core
|
||||||
- src/Radarr.Api.*/openapi.json
|
- src/Radarr.Api.*/openapi.json
|
||||||
|
|
||||||
@@ -363,7 +368,7 @@ stages:
|
|||||||
- bash: |
|
- bash: |
|
||||||
echo "Uploading source maps to sentry"
|
echo "Uploading source maps to sentry"
|
||||||
curl -sL https://sentry.io/get-cli/ | bash
|
curl -sL https://sentry.io/get-cli/ | bash
|
||||||
RELEASENAME="${RADARRVERSION}-${BUILD_SOURCEBRANCHNAME}"
|
RELEASENAME="Radarr@${RADARRVERSION}-${BUILD_SOURCEBRANCHNAME}"
|
||||||
sentry-cli releases new --finalize -p radarr -p radarr-ui -p radarr-update "${RELEASENAME}"
|
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 -p radarr-ui files "${RELEASENAME}" upload-sourcemaps _output/UI/ --rewrite
|
||||||
sentry-cli releases set-commits --auto "${RELEASENAME}"
|
sentry-cli releases set-commits --auto "${RELEASENAME}"
|
||||||
@@ -536,8 +541,8 @@ stages:
|
|||||||
testRunTitle: '$(testName) Unit Tests'
|
testRunTitle: '$(testName) Unit Tests'
|
||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
|
|
||||||
- job: Unit_LinuxCore_Postgres
|
- job: Unit_LinuxCore_Postgres14
|
||||||
displayName: Unit Native LinuxCore with Postgres Database
|
displayName: Unit Native LinuxCore with Postgres14 Database
|
||||||
dependsOn: Prepare
|
dependsOn: Prepare
|
||||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
variables:
|
variables:
|
||||||
@@ -589,7 +594,63 @@ stages:
|
|||||||
inputs:
|
inputs:
|
||||||
testResultsFormat: 'NUnit'
|
testResultsFormat: 'NUnit'
|
||||||
testResultsFiles: '**/TestResult.xml'
|
testResultsFiles: '**/TestResult.xml'
|
||||||
testRunTitle: 'LinuxCore Postgres Unit Tests'
|
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'
|
||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
|
|
||||||
- stage: Integration
|
- stage: Integration
|
||||||
@@ -675,8 +736,8 @@ stages:
|
|||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
displayName: Publish Test Results
|
displayName: Publish Test Results
|
||||||
|
|
||||||
- job: Integration_LinuxCore_Postgres
|
- job: Integration_LinuxCore_Postgres14
|
||||||
displayName: Integration Native LinuxCore with Postgres Database
|
displayName: Integration Native LinuxCore with Postgres14 Database
|
||||||
dependsOn: Prepare
|
dependsOn: Prepare
|
||||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
variables:
|
variables:
|
||||||
@@ -733,7 +794,70 @@ stages:
|
|||||||
inputs:
|
inputs:
|
||||||
testResultsFormat: 'NUnit'
|
testResultsFormat: 'NUnit'
|
||||||
testResultsFiles: '**/TestResult.xml'
|
testResultsFiles: '**/TestResult.xml'
|
||||||
testRunTitle: 'Integration LinuxCore Postgres Database Integration Tests'
|
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'
|
||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
displayName: Publish Test Results
|
displayName: Publish Test Results
|
||||||
|
|
||||||
|
|||||||
@@ -392,22 +392,21 @@ then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$FRONTEND" = "YES" ];
|
if [[ "$LINT" = "YES" || "$FRONTEND" = "YES" ]];
|
||||||
then
|
then
|
||||||
YarnInstall
|
YarnInstall
|
||||||
RunWebpack
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$LINT" = "YES" ];
|
if [ "$LINT" = "YES" ];
|
||||||
then
|
then
|
||||||
if [ -z "$FRONTEND" ];
|
|
||||||
then
|
|
||||||
YarnInstall
|
|
||||||
fi
|
|
||||||
|
|
||||||
LintUI
|
LintUI
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "$FRONTEND" = "YES" ];
|
||||||
|
then
|
||||||
|
RunWebpack
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$PACKAGES" = "YES" ];
|
if [ "$PACKAGES" = "YES" ];
|
||||||
then
|
then
|
||||||
UpdateVersionNumber
|
UpdateVersionNumber
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ module.exports = {
|
|||||||
plugins: [
|
plugins: [
|
||||||
// Stage 1
|
// Stage 1
|
||||||
'@babel/plugin-proposal-export-default-from',
|
'@babel/plugin-proposal-export-default-from',
|
||||||
['@babel/plugin-proposal-optional-chaining', { loose }],
|
['@babel/plugin-transform-optional-chaining', { loose }],
|
||||||
['@babel/plugin-proposal-nullish-coalescing-operator', { loose }],
|
['@babel/plugin-transform-nullish-coalescing-operator', { loose }],
|
||||||
|
|
||||||
// Stage 2
|
// Stage 2
|
||||||
'@babel/plugin-proposal-export-namespace-from',
|
'@babel/plugin-transform-export-namespace-from',
|
||||||
|
|
||||||
// Stage 3
|
// Stage 3
|
||||||
['@babel/plugin-proposal-class-properties', { loose }],
|
['@babel/plugin-transform-class-properties', { loose }],
|
||||||
'@babel/plugin-syntax-dynamic-import'
|
'@babel/plugin-syntax-dynamic-import'
|
||||||
],
|
],
|
||||||
env: {
|
env: {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ module.exports = (env) => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
entry: {
|
entry: {
|
||||||
index: 'index.js'
|
index: 'index.ts'
|
||||||
},
|
},
|
||||||
|
|
||||||
resolve: {
|
resolve: {
|
||||||
@@ -66,23 +66,23 @@ module.exports = (env) => {
|
|||||||
output: {
|
output: {
|
||||||
path: distFolder,
|
path: distFolder,
|
||||||
publicPath: '/',
|
publicPath: '/',
|
||||||
filename: '[name].js',
|
filename: '[name]-[contenthash].js',
|
||||||
sourceMapFilename: '[file].map'
|
sourceMapFilename: '[file].map'
|
||||||
},
|
},
|
||||||
|
|
||||||
optimization: {
|
optimization: {
|
||||||
moduleIds: 'deterministic',
|
moduleIds: 'deterministic',
|
||||||
chunkIds: 'named',
|
chunkIds: isProduction ? 'deterministic' : 'named'
|
||||||
splitChunks: {
|
|
||||||
chunks: 'initial',
|
|
||||||
name: 'vendors'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
performance: {
|
performance: {
|
||||||
hints: false
|
hints: false
|
||||||
},
|
},
|
||||||
|
|
||||||
|
experiments: {
|
||||||
|
topLevelAwait: true
|
||||||
|
},
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
__DEV__: !isProduction,
|
__DEV__: !isProduction,
|
||||||
@@ -90,13 +90,15 @@ module.exports = (env) => {
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
new MiniCssExtractPlugin({
|
new MiniCssExtractPlugin({
|
||||||
filename: 'Content/styles.css'
|
filename: 'Content/styles.css',
|
||||||
|
chunkFilename: 'Content/[id]-[chunkhash].css'
|
||||||
}),
|
}),
|
||||||
|
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
template: 'frontend/src/index.ejs',
|
template: 'frontend/src/index.ejs',
|
||||||
filename: 'index.html',
|
filename: 'index.html',
|
||||||
publicPath: '/'
|
publicPath: '/',
|
||||||
|
inject: false
|
||||||
}),
|
}),
|
||||||
|
|
||||||
new FileManagerPlugin({
|
new FileManagerPlugin({
|
||||||
@@ -233,6 +235,7 @@ module.exports = (env) => {
|
|||||||
{
|
{
|
||||||
loader: 'url-loader',
|
loader: 'url-loader',
|
||||||
options: {
|
options: {
|
||||||
|
limit: 10240,
|
||||||
mimetype: 'application/font-woff',
|
mimetype: 'application/font-woff',
|
||||||
emitFile: false,
|
emitFile: false,
|
||||||
name: 'Content/Fonts/[name].[ext]'
|
name: 'Content/Fonts/[name].[ext]'
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ class Blocklist extends Component {
|
|||||||
{
|
{
|
||||||
!isFetching && !!error &&
|
!isFetching && !!error &&
|
||||||
<Alert kind={kinds.DANGER}>
|
<Alert kind={kinds.DANGER}>
|
||||||
{translate('UnableToLoadBlocklist')}
|
{translate('BlocklistLoadError')}
|
||||||
</Alert>
|
</Alert>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +210,7 @@ class Blocklist extends Component {
|
|||||||
isOpen={isConfirmRemoveModalOpen}
|
isOpen={isConfirmRemoveModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('RemoveSelected')}
|
title={translate('RemoveSelected')}
|
||||||
message={translate('RemoveSelectedItemBlocklistMessageText')}
|
message={translate('RemoveSelectedBlocklistMessageText')}
|
||||||
confirmLabel={translate('RemoveSelected')}
|
confirmLabel={translate('RemoveSelected')}
|
||||||
onConfirm={this.onRemoveSelectedConfirmed}
|
onConfirm={this.onRemoveSelectedConfirmed}
|
||||||
onCancel={this.onConfirmRemoveModalClose}
|
onCancel={this.onConfirmRemoveModalClose}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import DescriptionListItemTitle from 'Components/DescriptionList/DescriptionList
|
|||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
import formatDateTime from 'Utilities/Date/formatDateTime';
|
import formatDateTime from 'Utilities/Date/formatDateTime';
|
||||||
import formatAge from 'Utilities/Number/formatAge';
|
import formatAge from 'Utilities/Number/formatAge';
|
||||||
|
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import styles from './HistoryDetails.css';
|
import styles from './HistoryDetails.css';
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ function HistoryDetails(props) {
|
|||||||
eventType,
|
eventType,
|
||||||
sourceTitle,
|
sourceTitle,
|
||||||
data,
|
data,
|
||||||
|
downloadId,
|
||||||
shortDateFormat,
|
shortDateFormat,
|
||||||
timeFormat
|
timeFormat
|
||||||
} = props;
|
} = props;
|
||||||
@@ -23,11 +25,11 @@ function HistoryDetails(props) {
|
|||||||
const {
|
const {
|
||||||
indexer,
|
indexer,
|
||||||
releaseGroup,
|
releaseGroup,
|
||||||
|
movieMatchType,
|
||||||
|
customFormatScore,
|
||||||
nzbInfoUrl,
|
nzbInfoUrl,
|
||||||
downloadClient,
|
downloadClient,
|
||||||
downloadClientName,
|
downloadClientName,
|
||||||
downloadId,
|
|
||||||
movieMatchType,
|
|
||||||
age,
|
age,
|
||||||
ageHours,
|
ageHours,
|
||||||
ageMinutes,
|
ageMinutes,
|
||||||
@@ -45,33 +47,31 @@ function HistoryDetails(props) {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{
|
{
|
||||||
!!indexer &&
|
indexer ?
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title={translate('Indexer')}
|
title={translate('Indexer')}
|
||||||
data={indexer}
|
data={indexer}
|
||||||
/>
|
/> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!!releaseGroup &&
|
releaseGroup ?
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
descriptionClassName={styles.description}
|
descriptionClassName={styles.description}
|
||||||
title={translate('ReleaseGroup')}
|
title={translate('ReleaseGroup')}
|
||||||
data={releaseGroup}
|
data={releaseGroup}
|
||||||
/>
|
/> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!!nzbInfoUrl &&
|
customFormatScore && customFormatScore !== '0' ?
|
||||||
<span>
|
<DescriptionListItem
|
||||||
<DescriptionListItemTitle>
|
title={translate('CustomFormatScore')}
|
||||||
Info URL
|
data={formatCustomFormatScore(customFormatScore)}
|
||||||
</DescriptionListItemTitle>
|
/> :
|
||||||
|
null
|
||||||
<DescriptionListItemDescription>
|
|
||||||
<Link to={nzbInfoUrl}>{nzbInfoUrl}</Link>
|
|
||||||
</DescriptionListItemDescription>
|
|
||||||
</span>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -84,6 +84,20 @@ function HistoryDetails(props) {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
nzbInfoUrl ?
|
||||||
|
<span>
|
||||||
|
<DescriptionListItemTitle>
|
||||||
|
{translate('InfoUrl')}
|
||||||
|
</DescriptionListItemTitle>
|
||||||
|
|
||||||
|
<DescriptionListItemDescription>
|
||||||
|
<Link to={nzbInfoUrl}>{nzbInfoUrl}</Link>
|
||||||
|
</DescriptionListItemDescription>
|
||||||
|
</span> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
downloadClientNameInfo ?
|
downloadClientNameInfo ?
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
@@ -94,27 +108,30 @@ function HistoryDetails(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!!downloadId &&
|
downloadId ?
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title={translate('GrabID')}
|
title={translate('GrabId')}
|
||||||
data={downloadId}
|
data={downloadId}
|
||||||
/>
|
/> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!!indexer &&
|
indexer ?
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title={translate('AgeWhenGrabbed')}
|
title={translate('AgeWhenGrabbed')}
|
||||||
data={formatAge(age, ageHours, ageMinutes)}
|
data={formatAge(age, ageHours, ageMinutes)}
|
||||||
/>
|
/> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!!publishedDate &&
|
publishedDate ?
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title={translate('PublishedDate')}
|
title={translate('PublishedDate')}
|
||||||
data={formatDateTime(publishedDate, shortDateFormat, timeFormat, { includeSeconds: true })}
|
data={formatDateTime(publishedDate, shortDateFormat, timeFormat, { includeSeconds: true })}
|
||||||
/>
|
/> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
</DescriptionList>
|
</DescriptionList>
|
||||||
);
|
);
|
||||||
@@ -134,11 +151,21 @@ function HistoryDetails(props) {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{
|
{
|
||||||
!!message &&
|
downloadId ?
|
||||||
|
<DescriptionListItem
|
||||||
|
title={translate('GrabId')}
|
||||||
|
data={downloadId}
|
||||||
|
/> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
message ?
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title={translate('Message')}
|
title={translate('Message')}
|
||||||
data={message}
|
data={message}
|
||||||
/>
|
/> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
</DescriptionList>
|
</DescriptionList>
|
||||||
);
|
);
|
||||||
@@ -146,6 +173,7 @@ function HistoryDetails(props) {
|
|||||||
|
|
||||||
if (eventType === 'downloadFolderImported') {
|
if (eventType === 'downloadFolderImported') {
|
||||||
const {
|
const {
|
||||||
|
customFormatScore,
|
||||||
droppedPath,
|
droppedPath,
|
||||||
importedPath
|
importedPath
|
||||||
} = data;
|
} = data;
|
||||||
@@ -159,21 +187,32 @@ function HistoryDetails(props) {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{
|
{
|
||||||
!!droppedPath &&
|
droppedPath ?
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
descriptionClassName={styles.description}
|
descriptionClassName={styles.description}
|
||||||
title={translate('Source')}
|
title={translate('Source')}
|
||||||
data={droppedPath}
|
data={droppedPath}
|
||||||
/>
|
/> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!!importedPath &&
|
importedPath ?
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
descriptionClassName={styles.description}
|
descriptionClassName={styles.description}
|
||||||
title={translate('ImportedTo')}
|
title={translate('ImportedTo')}
|
||||||
data={importedPath}
|
data={importedPath}
|
||||||
/>
|
/> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
customFormatScore && customFormatScore !== '0' ?
|
||||||
|
<DescriptionListItem
|
||||||
|
title={translate('CustomFormatScore')}
|
||||||
|
data={formatCustomFormatScore(customFormatScore)}
|
||||||
|
/> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
</DescriptionList>
|
</DescriptionList>
|
||||||
);
|
);
|
||||||
@@ -181,20 +220,21 @@ function HistoryDetails(props) {
|
|||||||
|
|
||||||
if (eventType === 'movieFileDeleted') {
|
if (eventType === 'movieFileDeleted') {
|
||||||
const {
|
const {
|
||||||
reason
|
reason,
|
||||||
|
customFormatScore
|
||||||
} = data;
|
} = data;
|
||||||
|
|
||||||
let reasonMessage = '';
|
let reasonMessage = '';
|
||||||
|
|
||||||
switch (reason) {
|
switch (reason) {
|
||||||
case 'Manual':
|
case 'Manual':
|
||||||
reasonMessage = translate('FileWasDeletedByViaUI');
|
reasonMessage = translate('DeletedReasonManual');
|
||||||
break;
|
break;
|
||||||
case 'MissingFromDisk':
|
case 'MissingFromDisk':
|
||||||
reasonMessage = translate('MissingFromDisk');
|
reasonMessage = translate('DeletedReasonMissingFromDisk');
|
||||||
break;
|
break;
|
||||||
case 'Upgrade':
|
case 'Upgrade':
|
||||||
reasonMessage = translate('FileWasDeletedByUpgrade');
|
reasonMessage = translate('DeletedReasonUpgrade');
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
reasonMessage = '';
|
reasonMessage = '';
|
||||||
@@ -211,6 +251,15 @@ function HistoryDetails(props) {
|
|||||||
title={translate('Reason')}
|
title={translate('Reason')}
|
||||||
data={reasonMessage}
|
data={reasonMessage}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{
|
||||||
|
customFormatScore && customFormatScore !== '0' ?
|
||||||
|
<DescriptionListItem
|
||||||
|
title={translate('CustomFormatScore')}
|
||||||
|
data={formatCustomFormatScore(customFormatScore)}
|
||||||
|
/> :
|
||||||
|
null
|
||||||
|
}
|
||||||
</DescriptionList>
|
</DescriptionList>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -262,11 +311,21 @@ function HistoryDetails(props) {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{
|
{
|
||||||
!!message &&
|
downloadId ?
|
||||||
|
<DescriptionListItem
|
||||||
|
title={translate('GrabId')}
|
||||||
|
data={downloadId}
|
||||||
|
/> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
message ?
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title={translate('Message')}
|
title={translate('Message')}
|
||||||
data={message}
|
data={message}
|
||||||
/>
|
/> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
</DescriptionList>
|
</DescriptionList>
|
||||||
);
|
);
|
||||||
@@ -287,6 +346,7 @@ HistoryDetails.propTypes = {
|
|||||||
eventType: PropTypes.string.isRequired,
|
eventType: PropTypes.string.isRequired,
|
||||||
sourceTitle: PropTypes.string.isRequired,
|
sourceTitle: PropTypes.string.isRequired,
|
||||||
data: PropTypes.object.isRequired,
|
data: PropTypes.object.isRequired,
|
||||||
|
downloadId: PropTypes.string,
|
||||||
shortDateFormat: PropTypes.string.isRequired,
|
shortDateFormat: PropTypes.string.isRequired,
|
||||||
timeFormat: PropTypes.string.isRequired
|
timeFormat: PropTypes.string.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,19 +15,19 @@ import styles from './HistoryDetailsModal.css';
|
|||||||
function getHeaderTitle(eventType) {
|
function getHeaderTitle(eventType) {
|
||||||
switch (eventType) {
|
switch (eventType) {
|
||||||
case 'grabbed':
|
case 'grabbed':
|
||||||
return 'Grabbed';
|
return translate('Grabbed');
|
||||||
case 'downloadFailed':
|
case 'downloadFailed':
|
||||||
return 'Download Failed';
|
return translate('DownloadFailed');
|
||||||
case 'downloadFolderImported':
|
case 'downloadFolderImported':
|
||||||
return 'Movie Imported';
|
return translate('MovieImported');
|
||||||
case 'movieFileDeleted':
|
case 'movieFileDeleted':
|
||||||
return 'Movie File Deleted';
|
return translate('MovieFileDeleted');
|
||||||
case 'movieFileRenamed':
|
case 'movieFileRenamed':
|
||||||
return 'Movie File Renamed';
|
return translate('MovieFileRenamed');
|
||||||
case 'downloadIgnored':
|
case 'downloadIgnored':
|
||||||
return 'Download Ignored';
|
return translate('DownloadIgnored');
|
||||||
default:
|
default:
|
||||||
return 'Unknown';
|
return translate('Unknown');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,6 +37,7 @@ function HistoryDetailsModal(props) {
|
|||||||
eventType,
|
eventType,
|
||||||
sourceTitle,
|
sourceTitle,
|
||||||
data,
|
data,
|
||||||
|
downloadId,
|
||||||
isMarkingAsFailed,
|
isMarkingAsFailed,
|
||||||
shortDateFormat,
|
shortDateFormat,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
@@ -59,6 +60,7 @@ function HistoryDetailsModal(props) {
|
|||||||
eventType={eventType}
|
eventType={eventType}
|
||||||
sourceTitle={sourceTitle}
|
sourceTitle={sourceTitle}
|
||||||
data={data}
|
data={data}
|
||||||
|
downloadId={downloadId}
|
||||||
shortDateFormat={shortDateFormat}
|
shortDateFormat={shortDateFormat}
|
||||||
timeFormat={timeFormat}
|
timeFormat={timeFormat}
|
||||||
/>
|
/>
|
||||||
@@ -73,7 +75,7 @@ function HistoryDetailsModal(props) {
|
|||||||
isSpinning={isMarkingAsFailed}
|
isSpinning={isMarkingAsFailed}
|
||||||
onPress={onMarkAsFailedPress}
|
onPress={onMarkAsFailedPress}
|
||||||
>
|
>
|
||||||
Mark as Failed
|
{translate('MarkAsFailed')}
|
||||||
</SpinnerButton>
|
</SpinnerButton>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,6 +95,7 @@ HistoryDetailsModal.propTypes = {
|
|||||||
eventType: PropTypes.string.isRequired,
|
eventType: PropTypes.string.isRequired,
|
||||||
sourceTitle: PropTypes.string.isRequired,
|
sourceTitle: PropTypes.string.isRequired,
|
||||||
data: PropTypes.object.isRequired,
|
data: PropTypes.object.isRequired,
|
||||||
|
downloadId: PropTypes.string,
|
||||||
isMarkingAsFailed: PropTypes.bool.isRequired,
|
isMarkingAsFailed: PropTypes.bool.isRequired,
|
||||||
shortDateFormat: PropTypes.string.isRequired,
|
shortDateFormat: PropTypes.string.isRequired,
|
||||||
timeFormat: PropTypes.string.isRequired,
|
timeFormat: PropTypes.string.isRequired,
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ class History extends Component {
|
|||||||
{
|
{
|
||||||
!isFetchingAny && hasError &&
|
!isFetchingAny && hasError &&
|
||||||
<Alert kind={kinds.DANGER}>
|
<Alert kind={kinds.DANGER}>
|
||||||
{translate('UnableToLoadHistory')}
|
{translate('HistoryLoadError')}
|
||||||
</Alert>
|
</Alert>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ class History extends Component {
|
|||||||
|
|
||||||
isPopulated && !hasError && !items.length &&
|
isPopulated && !hasError && !items.length &&
|
||||||
<Alert kind={kinds.INFO}>
|
<Alert kind={kinds.INFO}>
|
||||||
{translate('NoHistory')}
|
{translate('NoHistoryFound')}
|
||||||
</Alert>
|
</Alert>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import React from 'react';
|
|||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import { icons, kinds } from 'Helpers/Props';
|
import { icons, kinds } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
import styles from './HistoryEventTypeCell.css';
|
import styles from './HistoryEventTypeCell.css';
|
||||||
|
|
||||||
function getIconName(eventType) {
|
function getIconName(eventType) {
|
||||||
@@ -38,21 +39,21 @@ function getIconKind(eventType) {
|
|||||||
function getTooltip(eventType, data) {
|
function getTooltip(eventType, data) {
|
||||||
switch (eventType) {
|
switch (eventType) {
|
||||||
case 'grabbed':
|
case 'grabbed':
|
||||||
return `Movie grabbed from ${data.indexer} and sent to ${data.downloadClient}`;
|
return translate('MovieGrabbedHistoryTooltip', { indexer: data.indexer, downloadClient: data.downloadClient });
|
||||||
case 'movieFolderImported':
|
case 'movieFolderImported':
|
||||||
return 'Movie imported from movie folder';
|
return translate('MovieFolderImportedTooltip');
|
||||||
case 'downloadFolderImported':
|
case 'downloadFolderImported':
|
||||||
return 'Movie downloaded successfully and picked up from download client';
|
return translate('MovieImportedTooltip');
|
||||||
case 'downloadFailed':
|
case 'downloadFailed':
|
||||||
return 'Movie download failed';
|
return translate('MovieDownloadFailedTooltip');
|
||||||
case 'movieFileDeleted':
|
case 'movieFileDeleted':
|
||||||
return 'Movie file deleted';
|
return translate('MovieFileDeletedTooltip');
|
||||||
case 'movieFileRenamed':
|
case 'movieFileRenamed':
|
||||||
return 'Movie file renamed';
|
return translate('MovieFileRenamedTooltip');
|
||||||
case 'downloadIgnored':
|
case 'downloadIgnored':
|
||||||
return 'Movie Download Ignored';
|
return translate('MovieDownloadIgnoredTooltip');
|
||||||
default:
|
default:
|
||||||
return 'Unknown event';
|
return translate('UnknownEventTooltip');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import IconButton from 'Components/Link/IconButton';
|
|||||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import TableRow from 'Components/Table/TableRow';
|
import TableRow from 'Components/Table/TableRow';
|
||||||
import { icons } from 'Helpers/Props';
|
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||||
|
import { icons, tooltipPositions } from 'Helpers/Props';
|
||||||
import MovieFormats from 'Movie/MovieFormats';
|
import MovieFormats from 'Movie/MovieFormats';
|
||||||
import MovieLanguage from 'Movie/MovieLanguage';
|
import MovieLanguage from 'Movie/MovieLanguage';
|
||||||
import MovieQuality from 'Movie/MovieQuality';
|
import MovieQuality from 'Movie/MovieQuality';
|
||||||
@@ -63,6 +64,7 @@ class HistoryRow extends Component {
|
|||||||
sourceTitle,
|
sourceTitle,
|
||||||
date,
|
date,
|
||||||
data,
|
data,
|
||||||
|
downloadId,
|
||||||
isMarkingAsFailed,
|
isMarkingAsFailed,
|
||||||
columns,
|
columns,
|
||||||
shortDateFormat,
|
shortDateFormat,
|
||||||
@@ -176,7 +178,14 @@ class HistoryRow extends Component {
|
|||||||
key={name}
|
key={name}
|
||||||
className={styles.customFormatScore}
|
className={styles.customFormatScore}
|
||||||
>
|
>
|
||||||
{formatCustomFormatScore(customFormatScore)}
|
<Tooltip
|
||||||
|
anchor={formatCustomFormatScore(
|
||||||
|
customFormatScore,
|
||||||
|
customFormats.length
|
||||||
|
)}
|
||||||
|
tooltip={<MovieFormats formats={customFormats} />}
|
||||||
|
position={tooltipPositions.BOTTOM}
|
||||||
|
/>
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -225,6 +234,7 @@ class HistoryRow extends Component {
|
|||||||
eventType={eventType}
|
eventType={eventType}
|
||||||
sourceTitle={sourceTitle}
|
sourceTitle={sourceTitle}
|
||||||
data={data}
|
data={data}
|
||||||
|
downloadId={downloadId}
|
||||||
isMarkingAsFailed={isMarkingAsFailed}
|
isMarkingAsFailed={isMarkingAsFailed}
|
||||||
shortDateFormat={shortDateFormat}
|
shortDateFormat={shortDateFormat}
|
||||||
timeFormat={timeFormat}
|
timeFormat={timeFormat}
|
||||||
@@ -249,6 +259,7 @@ HistoryRow.propTypes = {
|
|||||||
sourceTitle: PropTypes.string.isRequired,
|
sourceTitle: PropTypes.string.isRequired,
|
||||||
date: PropTypes.string.isRequired,
|
date: PropTypes.string.isRequired,
|
||||||
data: PropTypes.object.isRequired,
|
data: PropTypes.object.isRequired,
|
||||||
|
downloadId: PropTypes.string,
|
||||||
isMarkingAsFailed: PropTypes.bool,
|
isMarkingAsFailed: PropTypes.bool,
|
||||||
markAsFailedError: PropTypes.object,
|
markAsFailedError: PropTypes.object,
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
@@ -257,4 +268,8 @@ HistoryRow.propTypes = {
|
|||||||
onMarkAsFailedPress: PropTypes.func.isRequired
|
onMarkAsFailedPress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
HistoryRow.defaultProps = {
|
||||||
|
customFormats: []
|
||||||
|
};
|
||||||
|
|
||||||
export default HistoryRow;
|
export default HistoryRow;
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ class Queue extends Component {
|
|||||||
{
|
{
|
||||||
!isRefreshing && hasError ?
|
!isRefreshing && hasError ?
|
||||||
<Alert kind={kinds.DANGER}>
|
<Alert kind={kinds.DANGER}>
|
||||||
{translate('FailedToLoadQueue')}
|
{translate('QueueLoadError')}
|
||||||
</Alert> :
|
</Alert> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
.progressBarContainer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// This file is automatically generated.
|
// This file is automatically generated.
|
||||||
// Please do not change this file!
|
// Please do not change this file!
|
||||||
interface CssExports {
|
interface CssExports {
|
||||||
'addCustomFormatMessage': string;
|
'progressBarContainer': string;
|
||||||
}
|
}
|
||||||
export const cssExports: CssExports;
|
export const cssExports: CssExports;
|
||||||
export default cssExports;
|
export default cssExports;
|
||||||
@@ -1,116 +1,71 @@
|
|||||||
import moment from 'moment';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import { icons, kinds } from 'Helpers/Props';
|
import Popover from 'Components/Tooltip/Popover';
|
||||||
|
import { icons, tooltipPositions } from 'Helpers/Props';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
|
import QueueStatus from './QueueStatus';
|
||||||
|
import styles from './QueueDetails.css';
|
||||||
|
|
||||||
function QueueDetails(props) {
|
function QueueDetails(props) {
|
||||||
const {
|
const {
|
||||||
title,
|
title,
|
||||||
size,
|
size,
|
||||||
sizeleft,
|
sizeleft,
|
||||||
estimatedCompletionTime,
|
|
||||||
status,
|
status,
|
||||||
trackedDownloadState,
|
trackedDownloadState,
|
||||||
trackedDownloadStatus,
|
trackedDownloadStatus,
|
||||||
|
statusMessages,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
progressBar
|
progressBar
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const progress = size ? (100 - sizeleft / size * 100) : 0;
|
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 (status === 'pending') {
|
if (
|
||||||
return (
|
(isDownloading || isPaused) &&
|
||||||
<Icon
|
!hasWarning &&
|
||||||
name={icons.PENDING}
|
!hasError
|
||||||
title={translate('ReleaseWillBeProcessedInterp', [moment(estimatedCompletionTime).fromNow()])}
|
) {
|
||||||
/>
|
const state = isPaused ? translate('Paused') : translate('Downloading');
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status === 'completed') {
|
if (progress < 5) {
|
||||||
if (errorMessage) {
|
|
||||||
return (
|
return (
|
||||||
<Icon
|
<Icon
|
||||||
name={icons.DOWNLOAD}
|
name={icons.DOWNLOADING}
|
||||||
kind={kinds.DANGER}
|
title={`${state} - ${progress.toFixed(1)}% ${title}`}
|
||||||
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 (
|
return (
|
||||||
<Icon
|
<Popover
|
||||||
name={icons.DOWNLOADING}
|
className={styles.progressBarContainer}
|
||||||
kind={kinds.DANGER}
|
anchor={progressBar}
|
||||||
title={translate('DownloadFailedInterp', [errorMessage])}
|
title={`${state} - ${progress.toFixed(1)}%`}
|
||||||
|
body={
|
||||||
|
<div>{title}</div>
|
||||||
|
}
|
||||||
|
position={tooltipPositions.LEFT}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status === 'failed') {
|
return (
|
||||||
return (
|
<QueueStatus
|
||||||
<Icon
|
sourceTitle={title}
|
||||||
name={icons.DOWNLOADING}
|
status={status}
|
||||||
kind={kinds.DANGER}
|
trackedDownloadStatus={trackedDownloadStatus}
|
||||||
title={translate('DownloadFailedCheckDownloadClientForMoreDetails')}
|
trackedDownloadState={trackedDownloadState}
|
||||||
/>
|
statusMessages={statusMessages}
|
||||||
);
|
errorMessage={errorMessage}
|
||||||
}
|
position={tooltipPositions.LEFT}
|
||||||
|
/>
|
||||||
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 = {
|
QueueDetails.propTypes = {
|
||||||
@@ -121,6 +76,7 @@ QueueDetails.propTypes = {
|
|||||||
status: PropTypes.string.isRequired,
|
status: PropTypes.string.isRequired,
|
||||||
trackedDownloadState: PropTypes.string.isRequired,
|
trackedDownloadState: PropTypes.string.isRequired,
|
||||||
trackedDownloadStatus: PropTypes.string.isRequired,
|
trackedDownloadStatus: PropTypes.string.isRequired,
|
||||||
|
statusMessages: PropTypes.arrayOf(PropTypes.object),
|
||||||
errorMessage: PropTypes.string,
|
errorMessage: PropTypes.string,
|
||||||
progressBar: PropTypes.node.isRequired
|
progressBar: PropTypes.node.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class QueueOptions extends Component {
|
|||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
name="includeUnknownMovieItems"
|
name="includeUnknownMovieItems"
|
||||||
value={includeUnknownMovieItems}
|
value={includeUnknownMovieItems}
|
||||||
helpText={translate('IncludeUnknownMovieItemsHelpText')}
|
helpText={translate('ShowUnknownMovieItemsHelpText')}
|
||||||
onChange={this.onOptionChange}
|
onChange={this.onOptionChange}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|||||||
@@ -16,6 +16,12 @@
|
|||||||
width: 150px;
|
width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.customFormatScore {
|
||||||
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
|
width: 55px;
|
||||||
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// Please do not change this file!
|
// Please do not change this file!
|
||||||
interface CssExports {
|
interface CssExports {
|
||||||
'actions': string;
|
'actions': string;
|
||||||
|
'customFormatScore': string;
|
||||||
'progress': string;
|
'progress': string;
|
||||||
'protocol': string;
|
'protocol': string;
|
||||||
'quality': string;
|
'quality': string;
|
||||||
|
|||||||
@@ -8,13 +8,15 @@ import ProgressBar from 'Components/ProgressBar';
|
|||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
||||||
import TableRow from 'Components/Table/TableRow';
|
import TableRow from 'Components/Table/TableRow';
|
||||||
import { icons, kinds } from 'Helpers/Props';
|
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||||
|
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
||||||
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
||||||
import MovieFormats from 'Movie/MovieFormats';
|
import MovieFormats from 'Movie/MovieFormats';
|
||||||
import MovieLanguage from 'Movie/MovieLanguage';
|
import MovieLanguage from 'Movie/MovieLanguage';
|
||||||
import MovieQuality from 'Movie/MovieQuality';
|
import MovieQuality from 'Movie/MovieQuality';
|
||||||
import MovieTitleLink from 'Movie/MovieTitleLink';
|
import MovieTitleLink from 'Movie/MovieTitleLink';
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
|
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import QueueStatusCell from './QueueStatusCell';
|
import QueueStatusCell from './QueueStatusCell';
|
||||||
import RemoveQueueItemModal from './RemoveQueueItemModal';
|
import RemoveQueueItemModal from './RemoveQueueItemModal';
|
||||||
@@ -42,14 +44,14 @@ class QueueRow extends Component {
|
|||||||
this.setState({ isRemoveQueueItemModalOpen: true });
|
this.setState({ isRemoveQueueItemModalOpen: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
onRemoveQueueItemModalConfirmed = (blocklist) => {
|
onRemoveQueueItemModalConfirmed = (blocklist, skipRedownload) => {
|
||||||
const {
|
const {
|
||||||
onRemoveQueueItemPress,
|
onRemoveQueueItemPress,
|
||||||
onQueueRowModalOpenOrClose
|
onQueueRowModalOpenOrClose
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
onQueueRowModalOpenOrClose(false);
|
onQueueRowModalOpenOrClose(false);
|
||||||
onRemoveQueueItemPress(blocklist);
|
onRemoveQueueItemPress(blocklist, skipRedownload);
|
||||||
|
|
||||||
this.setState({ isRemoveQueueItemModalOpen: false });
|
this.setState({ isRemoveQueueItemModalOpen: false });
|
||||||
};
|
};
|
||||||
@@ -88,6 +90,7 @@ class QueueRow extends Component {
|
|||||||
movie,
|
movie,
|
||||||
quality,
|
quality,
|
||||||
customFormats,
|
customFormats,
|
||||||
|
customFormatScore,
|
||||||
languages,
|
languages,
|
||||||
protocol,
|
protocol,
|
||||||
indexer,
|
indexer,
|
||||||
@@ -201,6 +204,24 @@ 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') {
|
if (name === 'protocol') {
|
||||||
return (
|
return (
|
||||||
<TableRowCell key={name}>
|
<TableRowCell key={name}>
|
||||||
@@ -365,6 +386,7 @@ QueueRow.propTypes = {
|
|||||||
movie: PropTypes.object,
|
movie: PropTypes.object,
|
||||||
quality: PropTypes.object.isRequired,
|
quality: PropTypes.object.isRequired,
|
||||||
customFormats: PropTypes.arrayOf(PropTypes.object),
|
customFormats: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
customFormatScore: PropTypes.number.isRequired,
|
||||||
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
|
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
protocol: PropTypes.string.isRequired,
|
protocol: PropTypes.string.isRequired,
|
||||||
indexer: PropTypes.string,
|
indexer: PropTypes.string,
|
||||||
@@ -390,6 +412,7 @@ QueueRow.propTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
QueueRow.defaultProps = {
|
QueueRow.defaultProps = {
|
||||||
|
customFormats: [],
|
||||||
isGrabbing: false,
|
isGrabbing: false,
|
||||||
isRemoving: false
|
isRemoving: false
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.noMessages {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
// This file is automatically generated.
|
// This file is automatically generated.
|
||||||
// Please do not change this file!
|
// Please do not change this file!
|
||||||
interface CssExports {
|
interface CssExports {
|
||||||
'enabled': string;
|
'noMessages': string;
|
||||||
'restriction': string;
|
|
||||||
}
|
}
|
||||||
export const cssExports: CssExports;
|
export const cssExports: CssExports;
|
||||||
export default cssExports;
|
export default cssExports;
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
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;
|
||||||
@@ -1,39 +1,11 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Icon from 'Components/Icon';
|
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import Popover from 'Components/Tooltip/Popover';
|
import { tooltipPositions } from 'Helpers/Props';
|
||||||
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
|
import QueueStatus from './QueueStatus';
|
||||||
import styles from './QueueStatusCell.css';
|
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) {
|
function QueueStatusCell(props) {
|
||||||
const {
|
const {
|
||||||
sourceTitle,
|
sourceTitle,
|
||||||
@@ -44,97 +16,16 @@ function QueueStatusCell(props) {
|
|||||||
errorMessage
|
errorMessage
|
||||||
} = props;
|
} = 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 (
|
return (
|
||||||
<TableRowCell className={styles.status}>
|
<TableRowCell className={styles.status}>
|
||||||
<Popover
|
<QueueStatus
|
||||||
anchor={
|
sourceTitle={sourceTitle}
|
||||||
<Icon
|
status={status}
|
||||||
name={iconName}
|
trackedDownloadStatus={trackedDownloadStatus}
|
||||||
kind={iconKind}
|
trackedDownloadState={trackedDownloadState}
|
||||||
/>
|
statusMessages={statusMessages}
|
||||||
}
|
errorMessage={errorMessage}
|
||||||
title={title}
|
|
||||||
body={hasWarning || hasError ? getDetailedPopoverBody(statusMessages) : sourceTitle}
|
|
||||||
position={tooltipPositions.RIGHT}
|
position={tooltipPositions.RIGHT}
|
||||||
canFlip={false}
|
|
||||||
/>
|
/>
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ class RemoveQueueItemModal extends Component {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
remove: true,
|
remove: true,
|
||||||
blocklist: false
|
blocklist: false,
|
||||||
|
skipRedownload: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +33,8 @@ class RemoveQueueItemModal extends Component {
|
|||||||
resetState = function() {
|
resetState = function() {
|
||||||
this.setState({
|
this.setState({
|
||||||
remove: true,
|
remove: true,
|
||||||
blocklist: false
|
blocklist: false,
|
||||||
|
skipRedownload: false
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -47,6 +49,10 @@ class RemoveQueueItemModal extends Component {
|
|||||||
this.setState({ blocklist: value });
|
this.setState({ blocklist: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onSkipRedownloadChange = ({ value }) => {
|
||||||
|
this.setState({ skipRedownload: value });
|
||||||
|
};
|
||||||
|
|
||||||
onRemoveConfirmed = () => {
|
onRemoveConfirmed = () => {
|
||||||
const state = this.state;
|
const state = this.state;
|
||||||
|
|
||||||
@@ -70,7 +76,7 @@ class RemoveQueueItemModal extends Component {
|
|||||||
isPending
|
isPending
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const { remove, blocklist } = this.state;
|
const { remove, blocklist, skipRedownload } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@@ -82,12 +88,12 @@ class RemoveQueueItemModal extends Component {
|
|||||||
onModalClose={this.onModalClose}
|
onModalClose={this.onModalClose}
|
||||||
>
|
>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
{translate('Remove')} - {sourceTitle}
|
{translate('RemoveQueueItem', { sourceTitle })}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<div>
|
<div>
|
||||||
{translate('RemoveFromQueueText', [sourceTitle])}
|
{translate('RemoveQueueItemConfirmation', { sourceTitle })}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -100,7 +106,7 @@ class RemoveQueueItemModal extends Component {
|
|||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
name="remove"
|
name="remove"
|
||||||
value={remove}
|
value={remove}
|
||||||
helpTextWarning={translate('RemoveHelpTextWarning')}
|
helpTextWarning={translate('RemoveFromDownloadClientHelpTextWarning')}
|
||||||
isDisabled={!canIgnore}
|
isDisabled={!canIgnore}
|
||||||
onChange={this.onRemoveChange}
|
onChange={this.onRemoveChange}
|
||||||
/>
|
/>
|
||||||
@@ -109,15 +115,30 @@ class RemoveQueueItemModal extends Component {
|
|||||||
|
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>{translate('BlocklistRelease')}</FormLabel>
|
<FormLabel>{translate('BlocklistRelease')}</FormLabel>
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
name="blocklist"
|
name="blocklist"
|
||||||
value={blocklist}
|
value={blocklist}
|
||||||
helpText={translate('BlocklistHelpText')}
|
helpText={translate('BlocklistReleaseHelpText')}
|
||||||
onChange={this.onBlocklistChange}
|
onChange={this.onBlocklistChange}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
{
|
||||||
|
blocklist ?
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('SkipRedownload')}</FormLabel>
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.CHECK}
|
||||||
|
name="skipRedownload"
|
||||||
|
value={skipRedownload}
|
||||||
|
helpText={translate('SkipRedownloadHelpText')}
|
||||||
|
onChange={this.onSkipRedownloadChange}
|
||||||
|
/>
|
||||||
|
</FormGroup> :
|
||||||
|
null
|
||||||
|
}
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
@@ -129,7 +150,7 @@ class RemoveQueueItemModal extends Component {
|
|||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
onPress={this.onRemoveConfirmed}
|
onPress={this.onRemoveConfirmed}
|
||||||
>
|
>
|
||||||
Remove
|
{translate('Remove')}
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ class RemoveQueueItemsModal extends Component {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
remove: true,
|
remove: true,
|
||||||
blocklist: false
|
blocklist: false,
|
||||||
|
skipRedownload: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +34,8 @@ class RemoveQueueItemsModal extends Component {
|
|||||||
resetState = function() {
|
resetState = function() {
|
||||||
this.setState({
|
this.setState({
|
||||||
remove: true,
|
remove: true,
|
||||||
blocklist: false
|
blocklist: false,
|
||||||
|
skipRedownload: false
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -48,6 +50,10 @@ class RemoveQueueItemsModal extends Component {
|
|||||||
this.setState({ blocklist: value });
|
this.setState({ blocklist: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onSkipRedownloadChange = ({ value }) => {
|
||||||
|
this.setState({ skipRedownload: value });
|
||||||
|
};
|
||||||
|
|
||||||
onRemoveConfirmed = () => {
|
onRemoveConfirmed = () => {
|
||||||
const state = this.state;
|
const state = this.state;
|
||||||
|
|
||||||
@@ -71,7 +77,7 @@ class RemoveQueueItemsModal extends Component {
|
|||||||
allPending
|
allPending
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const { remove, blocklist } = this.state;
|
const { remove, blocklist, skipRedownload } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@@ -88,7 +94,7 @@ class RemoveQueueItemsModal extends Component {
|
|||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<div className={styles.message}>
|
<div className={styles.message}>
|
||||||
{selectedCount > 1 ? translate('RemoveSelectedItemsQueueMessageText', selectedCount) : translate('RemoveSelectedItemQueueMessageText')}
|
{selectedCount > 1 ? translate('RemoveSelectedItemsQueueMessageText', { selectedCount }) : translate('RemoveSelectedItemQueueMessageText')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -117,11 +123,25 @@ class RemoveQueueItemsModal extends Component {
|
|||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
name="blocklist"
|
name="blocklist"
|
||||||
value={blocklist}
|
value={blocklist}
|
||||||
helpText={translate('BlocklistHelpText')}
|
helpText={translate('BlocklistReleaseHelpText')}
|
||||||
onChange={this.onBlocklistChange}
|
onChange={this.onBlocklistChange}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
{
|
||||||
|
blocklist ?
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('SkipRedownload')}</FormLabel>
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.CHECK}
|
||||||
|
name="skipRedownload"
|
||||||
|
value={skipRedownload}
|
||||||
|
helpText={translate('SkipRedownloadHelpText')}
|
||||||
|
onChange={this.onSkipRedownloadChange}
|
||||||
|
/>
|
||||||
|
</FormGroup> :
|
||||||
|
null
|
||||||
|
}
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ function TimeleftCell(props) {
|
|||||||
return (
|
return (
|
||||||
<TableRowCell
|
<TableRowCell
|
||||||
className={styles.timeleft}
|
className={styles.timeleft}
|
||||||
title={translate('DelayingDownloadUntilInterp', [date, time])}
|
title={translate('DelayingDownloadUntil', { date, time })}
|
||||||
>
|
>
|
||||||
-
|
-
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
@@ -41,7 +41,7 @@ function TimeleftCell(props) {
|
|||||||
return (
|
return (
|
||||||
<TableRowCell
|
<TableRowCell
|
||||||
className={styles.timeleft}
|
className={styles.timeleft}
|
||||||
title={translate('RetryingDownloadInterp', [date, time])}
|
title={translate('RetryingDownloadOn', { date, time })}
|
||||||
>
|
>
|
||||||
-
|
-
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ class AddNewMovie extends Component {
|
|||||||
!isFetching && !error && !items.length && !!term &&
|
!isFetching && !error && !items.length && !!term &&
|
||||||
<div className={styles.message}>
|
<div className={styles.message}>
|
||||||
<div className={styles.noResults}>
|
<div className={styles.noResults}>
|
||||||
{translate('CouldNotFindResults', [term])}
|
{translate('CouldNotFindResults', { term })}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{translate('YouCanAlsoSearch')}
|
{translate('YouCanAlsoSearch')}
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ class ImportMovie extends Component {
|
|||||||
rootFoldersPopulated &&
|
rootFoldersPopulated &&
|
||||||
!unmappedFolders.length ?
|
!unmappedFolders.length ?
|
||||||
<Alert kind={kinds.INFO}>
|
<Alert kind={kinds.INFO}>
|
||||||
{translate('AllMoviesInPathHaveBeenImported', [path])}
|
{translate('AllMoviesInPathHaveBeenImported', { path })}
|
||||||
</Alert> :
|
</Alert> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,17 +18,17 @@ import styles from './ImportMovieSelectFolder.css';
|
|||||||
const rootFolderColumns = [
|
const rootFolderColumns = [
|
||||||
{
|
{
|
||||||
name: 'path',
|
name: 'path',
|
||||||
label: translate('Path'),
|
label: () => translate('Path'),
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'freeSpace',
|
name: 'freeSpace',
|
||||||
label: translate('FreeSpace'),
|
label: () => translate('FreeSpace'),
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'unmappedFolders',
|
name: 'unmappedFolders',
|
||||||
label: translate('UnmappedFolders'),
|
label: () => translate('UnmappedFolders'),
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import Switch from 'Components/Router/Switch';
|
|||||||
import DiscoverMovieConnector from 'DiscoverMovie/DiscoverMovieConnector';
|
import DiscoverMovieConnector from 'DiscoverMovie/DiscoverMovieConnector';
|
||||||
import MovieDetailsPageConnector from 'Movie/Details/MovieDetailsPageConnector';
|
import MovieDetailsPageConnector from 'Movie/Details/MovieDetailsPageConnector';
|
||||||
import MovieIndex from 'Movie/Index/MovieIndex';
|
import MovieIndex from 'Movie/Index/MovieIndex';
|
||||||
import CustomFormatSettingsConnector from 'Settings/CustomFormats/CustomFormatSettingsConnector';
|
import CustomFormatSettingsPage from 'Settings/CustomFormats/CustomFormatSettingsPage';
|
||||||
import DownloadClientSettingsConnector from 'Settings/DownloadClients/DownloadClientSettingsConnector';
|
import DownloadClientSettingsConnector from 'Settings/DownloadClients/DownloadClientSettingsConnector';
|
||||||
import GeneralSettingsConnector from 'Settings/General/GeneralSettingsConnector';
|
import GeneralSettingsConnector from 'Settings/General/GeneralSettingsConnector';
|
||||||
import ImportListSettingsConnector from 'Settings/ImportLists/ImportListSettingsConnector';
|
import ImportListSettingsConnector from 'Settings/ImportLists/ImportListSettingsConnector';
|
||||||
@@ -148,7 +148,7 @@ function AppRoutes(props) {
|
|||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="/settings/customformats"
|
path="/settings/customformats"
|
||||||
component={CustomFormatSettingsConnector}
|
component={CustomFormatSettingsPage}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
.version {
|
.version {
|
||||||
margin: 0 3px;
|
margin: 0 3px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
font-family: var(--defaultFontFamily);
|
||||||
}
|
}
|
||||||
|
|
||||||
.maintenance {
|
.maintenance {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Button from 'Components/Link/Button';
|
import Button from 'Components/Link/Button';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
|
||||||
import ModalBody from 'Components/Modal/ModalBody';
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
import ModalContent from 'Components/Modal/ModalContent';
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
@@ -64,20 +65,20 @@ function AppUpdatedModalContent(props) {
|
|||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
{translate('RadarrUpdated')}
|
{translate('AppUpdated', { appName: 'Radarr' })}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<div dangerouslySetInnerHTML={{ __html: translate('VersionUpdateText', [`<span className=${styles.version}>${version}</span>`]) }} />
|
<div>
|
||||||
|
<InlineMarkdown data={translate('AppUpdatedVersion', { appName: 'Radarr', version })} blockClassName={styles.version} />
|
||||||
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
isPopulated && !error && !!update &&
|
isPopulated && !error && !!update &&
|
||||||
<div>
|
<div>
|
||||||
{
|
{
|
||||||
!update.changes &&
|
!update.changes &&
|
||||||
<div className={styles.maintenance}>
|
<div className={styles.maintenance}>{translate('MaintenanceRelease')}</div>
|
||||||
{translate('MaintenanceRelease')}
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,11 +28,11 @@ function ConnectionLostModal(props) {
|
|||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<div>
|
<div>
|
||||||
{translate('ConnectionLostMessage')}
|
{translate('ConnectionLostToBackend', { appName: 'Radarr' })}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.automatic}>
|
<div className={styles.automatic}>
|
||||||
{translate('ConnectionLostAutomaticMessage')}
|
{translate('ConnectionLostReconnect', { appName: 'Radarr' })}
|
||||||
</div>
|
</div>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
import InteractiveImportAppState from 'App/State/InteractiveImportAppState';
|
import InteractiveImportAppState from 'App/State/InteractiveImportAppState';
|
||||||
|
import CalendarAppState from './CalendarAppState';
|
||||||
|
import CommandAppState from './CommandAppState';
|
||||||
|
import MovieCollectionAppState from './MovieCollectionAppState';
|
||||||
import MovieFilesAppState from './MovieFilesAppState';
|
import MovieFilesAppState from './MovieFilesAppState';
|
||||||
import MoviesAppState, { MovieIndexAppState } from './MoviesAppState';
|
import MoviesAppState, { MovieIndexAppState } from './MoviesAppState';
|
||||||
|
import ParseAppState from './ParseAppState';
|
||||||
import QueueAppState from './QueueAppState';
|
import QueueAppState from './QueueAppState';
|
||||||
|
import RootFolderAppState from './RootFolderAppState';
|
||||||
import SettingsAppState from './SettingsAppState';
|
import SettingsAppState from './SettingsAppState';
|
||||||
|
import SystemAppState from './SystemAppState';
|
||||||
import TagsAppState from './TagsAppState';
|
import TagsAppState from './TagsAppState';
|
||||||
|
|
||||||
interface FilterBuilderPropOption {
|
interface FilterBuilderPropOption {
|
||||||
@@ -38,13 +44,19 @@ export interface CustomFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface AppState {
|
interface AppState {
|
||||||
movieFiles: MovieFilesAppState;
|
calendar: CalendarAppState;
|
||||||
|
commands: CommandAppState;
|
||||||
interactiveImport: InteractiveImportAppState;
|
interactiveImport: InteractiveImportAppState;
|
||||||
|
movieCollections: MovieCollectionAppState;
|
||||||
|
movieFiles: MovieFilesAppState;
|
||||||
movieIndex: MovieIndexAppState;
|
movieIndex: MovieIndexAppState;
|
||||||
settings: SettingsAppState;
|
|
||||||
movies: MoviesAppState;
|
movies: MoviesAppState;
|
||||||
tags: TagsAppState;
|
parse: ParseAppState;
|
||||||
queue: QueueAppState;
|
queue: QueueAppState;
|
||||||
|
rootFolders: RootFolderAppState;
|
||||||
|
settings: SettingsAppState;
|
||||||
|
system: SystemAppState;
|
||||||
|
tags: TagsAppState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AppState;
|
export default AppState;
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
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;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import AppSectionState from 'App/State/AppSectionState';
|
||||||
|
import Command from 'Commands/Command';
|
||||||
|
|
||||||
|
export type CommandAppState = AppSectionState<Command>;
|
||||||
|
|
||||||
|
export default CommandAppState;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import AppSectionState from 'App/State/AppSectionState';
|
import AppSectionState from 'App/State/AppSectionState';
|
||||||
import RecentFolder from 'InteractiveImport/Folder/RecentFolder';
|
import RecentFolder from 'InteractiveImport/Folder/RecentFolder';
|
||||||
import ImportMode from '../../InteractiveImport/ImportMode';
|
import ImportMode from 'InteractiveImport/ImportMode';
|
||||||
import InteractiveImport from '../../InteractiveImport/InteractiveImport';
|
import InteractiveImport from 'InteractiveImport/InteractiveImport';
|
||||||
|
|
||||||
interface InteractiveImportAppState extends AppSectionState<InteractiveImport> {
|
interface InteractiveImportAppState extends AppSectionState<InteractiveImport> {
|
||||||
originalItems: InteractiveImport[];
|
originalItems: InteractiveImport[];
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import AppSectionState from 'App/State/AppSectionState';
|
||||||
|
import MovieCollection from 'typings/MovieCollection';
|
||||||
|
|
||||||
|
type MovieCollectionAppState = AppSectionState<MovieCollection>;
|
||||||
|
|
||||||
|
export default MovieCollectionAppState;
|
||||||
@@ -22,6 +22,9 @@ export interface MovieIndexAppState {
|
|||||||
showQualityProfile: boolean;
|
showQualityProfile: boolean;
|
||||||
showReleaseDate: boolean;
|
showReleaseDate: boolean;
|
||||||
showCinemaRelease: boolean;
|
showCinemaRelease: boolean;
|
||||||
|
showTmdbRating: boolean;
|
||||||
|
showImdbRating: boolean;
|
||||||
|
showRottenTomatoesRating: boolean;
|
||||||
showSearchAction: boolean;
|
showSearchAction: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
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;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import AppSectionState, {
|
||||||
|
AppSectionDeleteState,
|
||||||
|
AppSectionSaveState,
|
||||||
|
} from 'App/State/AppSectionState';
|
||||||
|
import RootFolder from 'typings/RootFolder';
|
||||||
|
|
||||||
|
interface RootFolderAppState
|
||||||
|
extends AppSectionState<RootFolder>,
|
||||||
|
AppSectionDeleteState,
|
||||||
|
AppSectionSaveState {}
|
||||||
|
|
||||||
|
export default RootFolderAppState;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import AppSectionState, {
|
import AppSectionState, {
|
||||||
AppSectionDeleteState,
|
AppSectionDeleteState,
|
||||||
|
AppSectionItemState,
|
||||||
AppSectionSaveState,
|
AppSectionSaveState,
|
||||||
AppSectionSchemaState,
|
AppSectionSchemaState,
|
||||||
} from 'App/State/AppSectionState';
|
} from 'App/State/AppSectionState';
|
||||||
@@ -7,6 +8,7 @@ import Language from 'Language/Language';
|
|||||||
import DownloadClient from 'typings/DownloadClient';
|
import DownloadClient from 'typings/DownloadClient';
|
||||||
import ImportList from 'typings/ImportList';
|
import ImportList from 'typings/ImportList';
|
||||||
import Indexer from 'typings/Indexer';
|
import Indexer from 'typings/Indexer';
|
||||||
|
import IndexerFlag from 'typings/IndexerFlag';
|
||||||
import Notification from 'typings/Notification';
|
import Notification from 'typings/Notification';
|
||||||
import QualityProfile from 'typings/QualityProfile';
|
import QualityProfile from 'typings/QualityProfile';
|
||||||
import { UiSettings } from 'typings/UiSettings';
|
import { UiSettings } from 'typings/UiSettings';
|
||||||
@@ -34,17 +36,19 @@ export interface QualityProfilesAppState
|
|||||||
extends AppSectionState<QualityProfile>,
|
extends AppSectionState<QualityProfile>,
|
||||||
AppSectionSchemaState<QualityProfile> {}
|
AppSectionSchemaState<QualityProfile> {}
|
||||||
|
|
||||||
|
export type IndexerFlagSettingsAppState = AppSectionState<IndexerFlag>;
|
||||||
export type LanguageSettingsAppState = AppSectionState<Language>;
|
export type LanguageSettingsAppState = AppSectionState<Language>;
|
||||||
export type UiSettingsAppState = AppSectionState<UiSettings>;
|
export type UiSettingsAppState = AppSectionItemState<UiSettings>;
|
||||||
|
|
||||||
interface SettingsAppState {
|
interface SettingsAppState {
|
||||||
downloadClients: DownloadClientAppState;
|
downloadClients: DownloadClientAppState;
|
||||||
importLists: ImportListAppState;
|
importLists: ImportListAppState;
|
||||||
|
indexerFlags: IndexerFlagSettingsAppState;
|
||||||
indexers: IndexerAppState;
|
indexers: IndexerAppState;
|
||||||
|
languages: LanguageSettingsAppState;
|
||||||
notifications: NotificationAppState;
|
notifications: NotificationAppState;
|
||||||
language: LanguageSettingsAppState;
|
|
||||||
uiSettings: UiSettingsAppState;
|
|
||||||
qualityProfiles: QualityProfilesAppState;
|
qualityProfiles: QualityProfilesAppState;
|
||||||
|
ui: UiSettingsAppState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SettingsAppState;
|
export default SettingsAppState;
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import SystemStatus from 'typings/SystemStatus';
|
||||||
|
import { AppSectionItemState } from './AppSectionState';
|
||||||
|
|
||||||
|
export type SystemStatusAppState = AppSectionItemState<SystemStatus>;
|
||||||
|
|
||||||
|
interface SystemAppState {
|
||||||
|
status: SystemStatusAppState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SystemAppState;
|
||||||
@@ -1,12 +1,32 @@
|
|||||||
import ModelBase from 'App/ModelBase';
|
import ModelBase from 'App/ModelBase';
|
||||||
import AppSectionState, {
|
import AppSectionState, {
|
||||||
AppSectionDeleteState,
|
AppSectionDeleteState,
|
||||||
|
AppSectionSaveState,
|
||||||
} from 'App/State/AppSectionState';
|
} from 'App/State/AppSectionState';
|
||||||
|
|
||||||
export interface Tag extends ModelBase {
|
export interface Tag extends ModelBase {
|
||||||
label: string;
|
label: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TagsAppState extends AppSectionState<Tag>, AppSectionDeleteState {}
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
export default TagsAppState;
|
export default TagsAppState;
|
||||||
|
|||||||
@@ -1,17 +1,28 @@
|
|||||||
.event {
|
.event {
|
||||||
display: flex;
|
position: relative;
|
||||||
overflow-x: hidden;
|
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
border-bottom: 1px solid var(--borderColor);
|
border-bottom: 1px solid var(--borderColor);
|
||||||
font-size: $defaultFontSize;
|
}
|
||||||
|
|
||||||
|
.underlay {
|
||||||
|
@add-mixin cover;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--tableRowHoverBackgroundColor);
|
background-color: var(--tableRowHoverBackgroundColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.link {
|
.overlay {
|
||||||
composes: link from '~Calendar/Events/CalendarEvent.css';
|
@add-mixin linkOverlay;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
overflow-x: hidden;
|
||||||
|
font-size: $defaultFontSize;
|
||||||
|
|
||||||
|
&:global(.colorImpaired) {
|
||||||
|
border-left-width: 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.eventWrapper {
|
.eventWrapper {
|
||||||
@@ -44,6 +55,8 @@
|
|||||||
|
|
||||||
.statusIcon {
|
.statusIcon {
|
||||||
margin-left: 3px;
|
margin-left: 3px;
|
||||||
|
cursor: default;
|
||||||
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -95,6 +108,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dateIcon {
|
.releaseIcon {
|
||||||
|
margin-right: 20px;
|
||||||
width: 25px;
|
width: 25px;
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,18 +3,19 @@
|
|||||||
interface CssExports {
|
interface CssExports {
|
||||||
'continuing': string;
|
'continuing': string;
|
||||||
'date': string;
|
'date': string;
|
||||||
'dateIcon': string;
|
|
||||||
'downloaded': string;
|
'downloaded': string;
|
||||||
'event': string;
|
'event': string;
|
||||||
'eventWrapper': string;
|
'eventWrapper': string;
|
||||||
'genres': string;
|
'genres': string;
|
||||||
'link': string;
|
|
||||||
'missingMonitored': string;
|
'missingMonitored': string;
|
||||||
'missingUnmonitored': string;
|
'missingUnmonitored': string;
|
||||||
'movieTitle': string;
|
'movieTitle': string;
|
||||||
|
'overlay': string;
|
||||||
'queue': string;
|
'queue': string;
|
||||||
|
'releaseIcon': string;
|
||||||
'statusIcon': string;
|
'statusIcon': string;
|
||||||
'time': string;
|
'time': string;
|
||||||
|
'underlay': string;
|
||||||
'unmonitored': string;
|
'unmonitored': string;
|
||||||
}
|
}
|
||||||
export const cssExports: CssExports;
|
export const cssExports: CssExports;
|
||||||
|
|||||||
@@ -87,25 +87,24 @@ class AgendaEvent extends Component {
|
|||||||
const link = `/movie/${titleSlug}`;
|
const link = `/movie/${titleSlug}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={styles.event}>
|
||||||
<Link
|
<Link
|
||||||
className={classNames(
|
className={styles.underlay}
|
||||||
styles.event,
|
|
||||||
styles.link
|
|
||||||
)}
|
|
||||||
to={link}
|
to={link}
|
||||||
>
|
/>
|
||||||
<div className={styles.dateIcon}>
|
|
||||||
|
<div className={styles.overlay}>
|
||||||
|
<div className={styles.date}>
|
||||||
|
{(showDate) ? startTime.format(longDateFormat) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.releaseIcon}>
|
||||||
<Icon
|
<Icon
|
||||||
name={releaseIcon}
|
name={releaseIcon}
|
||||||
kind={kinds.DEFAULT}
|
kind={kinds.DEFAULT}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.date}>
|
|
||||||
{(showDate) ? startTime.format(longDateFormat) : null}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
styles.eventWrapper,
|
styles.eventWrapper,
|
||||||
@@ -143,9 +142,7 @@ class AgendaEvent extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
showCutoffUnmetIcon &&
|
showCutoffUnmetIcon && !!movieFile && movieFile.qualityCutoffNotMet &&
|
||||||
!!movieFile &&
|
|
||||||
movieFile.qualityCutoffNotMet &&
|
|
||||||
<Icon
|
<Icon
|
||||||
className={styles.statusIcon}
|
className={styles.statusIcon}
|
||||||
name={icons.MOVIE_FILE}
|
name={icons.MOVIE_FILE}
|
||||||
@@ -154,7 +151,7 @@ class AgendaEvent extends Component {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import NoMovie from 'Movie/NoMovie';
|
|||||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import CalendarConnector from './CalendarConnector';
|
import CalendarConnector from './CalendarConnector';
|
||||||
|
import CalendarFilterModal from './CalendarFilterModal';
|
||||||
import CalendarLinkModal from './iCal/CalendarLinkModal';
|
import CalendarLinkModal from './iCal/CalendarLinkModal';
|
||||||
import LegendConnector from './Legend/LegendConnector';
|
import LegendConnector from './Legend/LegendConnector';
|
||||||
import CalendarOptionsModal from './Options/CalendarOptionsModal';
|
import CalendarOptionsModal from './Options/CalendarOptionsModal';
|
||||||
@@ -83,6 +84,7 @@ class CalendarPage extends Component {
|
|||||||
movieIsFetching,
|
movieIsFetching,
|
||||||
movieIsPopulated,
|
movieIsPopulated,
|
||||||
missingMovieIds,
|
missingMovieIds,
|
||||||
|
customFilters,
|
||||||
isRssSyncExecuting,
|
isRssSyncExecuting,
|
||||||
isSearchingForMissing,
|
isSearchingForMissing,
|
||||||
useCurrentPage,
|
useCurrentPage,
|
||||||
@@ -137,7 +139,8 @@ class CalendarPage extends Component {
|
|||||||
isDisabled={!hasMovie}
|
isDisabled={!hasMovie}
|
||||||
selectedFilterKey={selectedFilterKey}
|
selectedFilterKey={selectedFilterKey}
|
||||||
filters={filters}
|
filters={filters}
|
||||||
customFilters={[]}
|
customFilters={customFilters}
|
||||||
|
filterModalConnectorComponent={CalendarFilterModal}
|
||||||
onFilterSelect={onFilterSelect}
|
onFilterSelect={onFilterSelect}
|
||||||
/>
|
/>
|
||||||
</PageToolbarSection>
|
</PageToolbarSection>
|
||||||
@@ -208,6 +211,7 @@ CalendarPage.propTypes = {
|
|||||||
movieIsFetching: PropTypes.bool.isRequired,
|
movieIsFetching: PropTypes.bool.isRequired,
|
||||||
movieIsPopulated: PropTypes.bool.isRequired,
|
movieIsPopulated: PropTypes.bool.isRequired,
|
||||||
missingMovieIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
missingMovieIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
|
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
isRssSyncExecuting: PropTypes.bool.isRequired,
|
isRssSyncExecuting: PropTypes.bool.isRequired,
|
||||||
isSearchingForMissing: PropTypes.bool.isRequired,
|
isSearchingForMissing: PropTypes.bool.isRequired,
|
||||||
useCurrentPage: PropTypes.bool.isRequired,
|
useCurrentPage: PropTypes.bool.isRequired,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import * as commandNames from 'Commands/commandNames';
|
|||||||
import withCurrentPage from 'Components/withCurrentPage';
|
import withCurrentPage from 'Components/withCurrentPage';
|
||||||
import { searchMissing, setCalendarDaysCount, setCalendarFilter } from 'Store/Actions/calendarActions';
|
import { searchMissing, setCalendarDaysCount, setCalendarFilter } from 'Store/Actions/calendarActions';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
|
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
|
||||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||||
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
|
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
|
||||||
import createMovieCountSelector from 'Store/Selectors/createMovieCountSelector';
|
import createMovieCountSelector from 'Store/Selectors/createMovieCountSelector';
|
||||||
@@ -59,6 +60,7 @@ function createMapStateToProps() {
|
|||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.calendar.selectedFilterKey,
|
(state) => state.calendar.selectedFilterKey,
|
||||||
(state) => state.calendar.filters,
|
(state) => state.calendar.filters,
|
||||||
|
createCustomFiltersSelector('calendar'),
|
||||||
createMovieCountSelector(),
|
createMovieCountSelector(),
|
||||||
createUISettingsSelector(),
|
createUISettingsSelector(),
|
||||||
createMissingMovieIdsSelector(),
|
createMissingMovieIdsSelector(),
|
||||||
@@ -67,6 +69,7 @@ function createMapStateToProps() {
|
|||||||
(
|
(
|
||||||
selectedFilterKey,
|
selectedFilterKey,
|
||||||
filters,
|
filters,
|
||||||
|
customFilters,
|
||||||
movieCount,
|
movieCount,
|
||||||
uiSettings,
|
uiSettings,
|
||||||
missingMovieIds,
|
missingMovieIds,
|
||||||
@@ -76,6 +79,7 @@ function createMapStateToProps() {
|
|||||||
return {
|
return {
|
||||||
selectedFilterKey,
|
selectedFilterKey,
|
||||||
filters,
|
filters,
|
||||||
|
customFilters,
|
||||||
colorImpairedMode: uiSettings.enableColorImpairedMode,
|
colorImpairedMode: uiSettings.enableColorImpairedMode,
|
||||||
hasMovie: !!movieCount.count,
|
hasMovie: !!movieCount.count,
|
||||||
movieError: movieCount.error,
|
movieError: movieCount.error,
|
||||||
|
|||||||
@@ -1,9 +1,22 @@
|
|||||||
|
$fullColorGradient: rgba(244, 245, 246, 0.2);
|
||||||
|
|
||||||
.event {
|
.event {
|
||||||
overflow-x: hidden;
|
position: relative;
|
||||||
margin: 4px 2px;
|
margin: 4px 2px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
border-bottom: 1px solid var(--calendarBorderColor);
|
border-bottom: 1px solid var(--calendarBorderColor);
|
||||||
border-left: 4px 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;
|
font-size: 12px;
|
||||||
|
|
||||||
&:global(.colorImpaired) {
|
&:global(.colorImpaired) {
|
||||||
@@ -11,18 +24,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.link {
|
|
||||||
composes: link from '~Components/Link/Link.css';
|
|
||||||
|
|
||||||
display: block;
|
|
||||||
color: var(--defaultColor);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--defaultColor);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.info,
|
.info,
|
||||||
.movieInfo {
|
.movieInfo {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -44,8 +45,15 @@
|
|||||||
font-size: $defaultFontSize;
|
font-size: $defaultFontSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.statusContainer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.statusIcon {
|
.statusIcon {
|
||||||
margin-left: 3px;
|
margin-left: 3px;
|
||||||
|
cursor: default;
|
||||||
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -55,35 +63,84 @@
|
|||||||
.downloaded {
|
.downloaded {
|
||||||
border-left-color: var(--successColor) !important;
|
border-left-color: var(--successColor) !important;
|
||||||
|
|
||||||
|
&:global(.fullColor) {
|
||||||
|
background-color: rgba(39, 194, 76, 0.4) !important;
|
||||||
|
}
|
||||||
|
|
||||||
&:global(.colorImpaired) {
|
&:global(.colorImpaired) {
|
||||||
border-left-color: color(var(--successColor), saturation(+15%)) !important;
|
border-left-color: color(#27c24c saturation(+15%)) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.queue {
|
.queue {
|
||||||
border-left-color: var(--purple) !important;
|
border-left-color: var(--purple) !important;
|
||||||
|
|
||||||
|
&:global(.fullColor) {
|
||||||
|
background-color: rgba(122, 67, 182, 0.4) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.unmonitored {
|
.unmonitored {
|
||||||
border-left-color: var(--gray) !important;
|
border-left-color: var(--gray) !important;
|
||||||
|
|
||||||
|
&:global(.fullColor) {
|
||||||
|
background-color: rgba(173, 173, 173, 0.5) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:global(.colorImpaired) {
|
||||||
|
background: repeating-linear-gradient(45deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:global(.fullColor.colorImpaired) {
|
||||||
|
background: repeating-linear-gradient(45deg, $fullColorGradient, $fullColorGradient 5px, transparent 5px, transparent 10px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.missingUnmonitored {
|
.missingUnmonitored {
|
||||||
border-left-color: var(--warningColor) !important;
|
border-left-color: var(--warningColor) !important;
|
||||||
|
|
||||||
|
&:global(.fullColor) {
|
||||||
|
background-color: rgba(255, 165, 0, 0.6) !important;
|
||||||
|
}
|
||||||
|
|
||||||
&:global(.colorImpaired) {
|
&:global(.colorImpaired) {
|
||||||
background: repeating-linear-gradient(45deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px);
|
background: repeating-linear-gradient(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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.missingMonitored {
|
.missingMonitored {
|
||||||
border-left-color: var(--dangerColor) !important;
|
border-left-color: var(--dangerColor) !important;
|
||||||
|
|
||||||
|
&:global(.fullColor) {
|
||||||
|
background-color: rgba(240, 80, 80, 0.6) !important;
|
||||||
|
}
|
||||||
|
|
||||||
&:global(.colorImpaired) {
|
&:global(.colorImpaired) {
|
||||||
|
border-left-color: color(#f05050 saturation(+15%)) !important;
|
||||||
background: repeating-linear-gradient(90deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px);
|
background: repeating-linear-gradient(90deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:global(.fullColor.colorImpaired) {
|
||||||
|
background: repeating-linear-gradient(90deg, $fullColorGradient, $fullColorGradient 5px, transparent 5px, transparent 10px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.continuing {
|
.continuing {
|
||||||
border-left-color: var(--primaryColor) !important;
|
border-left-color: var(--primaryColor) !important;
|
||||||
|
|
||||||
|
&:global(.fullColor) {
|
||||||
|
background-color: rgba(93, 156, 236, 0.4) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:global(.colorImpaired) {
|
||||||
|
background: repeating-linear-gradient(90deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:global(.fullColor.colorImpaired) {
|
||||||
|
background: repeating-linear-gradient(90deg, $fullColorGradient, $fullColorGradient 5px, transparent 5px, transparent 10px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,15 @@ interface CssExports {
|
|||||||
'event': string;
|
'event': string;
|
||||||
'genres': string;
|
'genres': string;
|
||||||
'info': string;
|
'info': string;
|
||||||
'link': string;
|
|
||||||
'missingMonitored': string;
|
'missingMonitored': string;
|
||||||
'missingUnmonitored': string;
|
'missingUnmonitored': string;
|
||||||
'movieInfo': string;
|
'movieInfo': string;
|
||||||
'movieTitle': string;
|
'movieTitle': string;
|
||||||
|
'overlay': string;
|
||||||
'queue': string;
|
'queue': string;
|
||||||
|
'statusContainer': string;
|
||||||
'statusIcon': string;
|
'statusIcon': string;
|
||||||
|
'underlay': string;
|
||||||
'unmonitored': string;
|
'unmonitored': string;
|
||||||
}
|
}
|
||||||
export const cssExports: CssExports;
|
export const cssExports: CssExports;
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class CalendarEvent extends Component {
|
|||||||
queueItem,
|
queueItem,
|
||||||
showMovieInformation,
|
showMovieInformation,
|
||||||
showCutoffUnmetIcon,
|
showCutoffUnmetIcon,
|
||||||
|
fullColorEvents,
|
||||||
colorImpairedMode,
|
colorImpairedMode,
|
||||||
date
|
date
|
||||||
} = this.props;
|
} = this.props;
|
||||||
@@ -56,64 +57,71 @@ class CalendarEvent extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div
|
||||||
|
className={classNames(
|
||||||
|
styles.event,
|
||||||
|
styles[statusStyle],
|
||||||
|
colorImpairedMode && 'colorImpaired',
|
||||||
|
fullColorEvents && 'fullColor'
|
||||||
|
)}
|
||||||
|
>
|
||||||
<Link
|
<Link
|
||||||
className={classNames(
|
className={styles.underlay}
|
||||||
styles.event,
|
|
||||||
styles.link,
|
|
||||||
styles[statusStyle],
|
|
||||||
colorImpairedMode && 'colorImpaired'
|
|
||||||
)}
|
|
||||||
// component="div"
|
|
||||||
to={link}
|
to={link}
|
||||||
>
|
/>
|
||||||
|
|
||||||
|
<div className={styles.overlay} >
|
||||||
<div className={styles.info}>
|
<div className={styles.info}>
|
||||||
<div className={styles.movieTitle}>
|
<div className={styles.movieTitle}>
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
<div className={styles.statusContainer}>
|
||||||
!!queueItem &&
|
{
|
||||||
<span className={styles.statusIcon}>
|
queueItem ?
|
||||||
<CalendarEventQueueDetails
|
<span className={styles.statusIcon}>
|
||||||
{...queueItem}
|
<CalendarEventQueueDetails
|
||||||
/>
|
{...queueItem}
|
||||||
</span>
|
/>
|
||||||
}
|
</span> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!queueItem && grabbed &&
|
!queueItem && grabbed ?
|
||||||
<Icon
|
<Icon
|
||||||
className={styles.statusIcon}
|
className={styles.statusIcon}
|
||||||
name={icons.DOWNLOADING}
|
name={icons.DOWNLOADING}
|
||||||
title={translate('MovieIsDownloading')}
|
title={translate('MovieIsDownloading')}
|
||||||
/>
|
/> :
|
||||||
}
|
null
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
showCutoffUnmetIcon &&
|
showCutoffUnmetIcon && !!movieFile && movieFile.qualityCutoffNotMet ?
|
||||||
!!movieFile &&
|
<Icon
|
||||||
movieFile.qualityCutoffNotMet &&
|
className={styles.statusIcon}
|
||||||
<Icon
|
name={icons.MOVIE_FILE}
|
||||||
className={styles.statusIcon}
|
kind={kinds.WARNING}
|
||||||
name={icons.MOVIE_FILE}
|
title={translate('QualityCutoffHasNotBeenMet')}
|
||||||
kind={kinds.WARNING}
|
/> :
|
||||||
title={translate('QualityCutoffHasNotBeenMet')}
|
null
|
||||||
/>
|
}
|
||||||
}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
showMovieInformation &&
|
showMovieInformation ?
|
||||||
<div className={styles.movieInfo}>
|
<div className={styles.movieInfo}>
|
||||||
<div className={styles.genres}>
|
<div className={styles.genres}>
|
||||||
{joinedGenres}
|
{joinedGenres}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
showMovieInformation &&
|
showMovieInformation ?
|
||||||
<div className={styles.movieInfo}>
|
<div className={styles.movieInfo}>
|
||||||
<div className={styles.genres}>
|
<div className={styles.genres}>
|
||||||
{eventType.join(', ')}
|
{eventType.join(', ')}
|
||||||
@@ -121,10 +129,10 @@ class CalendarEvent extends Component {
|
|||||||
<div>
|
<div>
|
||||||
{certification}
|
{certification}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
</Link>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -147,6 +155,7 @@ CalendarEvent.propTypes = {
|
|||||||
queueItem: PropTypes.object,
|
queueItem: PropTypes.object,
|
||||||
showMovieInformation: PropTypes.bool.isRequired,
|
showMovieInformation: PropTypes.bool.isRequired,
|
||||||
showCutoffUnmetIcon: PropTypes.bool.isRequired,
|
showCutoffUnmetIcon: PropTypes.bool.isRequired,
|
||||||
|
fullColorEvents: PropTypes.bool.isRequired,
|
||||||
timeFormat: PropTypes.string.isRequired,
|
timeFormat: PropTypes.string.isRequired,
|
||||||
colorImpairedMode: PropTypes.bool.isRequired,
|
colorImpairedMode: PropTypes.bool.isRequired,
|
||||||
date: PropTypes.string.isRequired
|
date: PropTypes.string.isRequired
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import PropTypes from 'prop-types';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import QueueDetails from 'Activity/Queue/QueueDetails';
|
import QueueDetails from 'Activity/Queue/QueueDetails';
|
||||||
import CircularProgressBar from 'Components/CircularProgressBar';
|
import CircularProgressBar from 'Components/CircularProgressBar';
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
|
|
||||||
function CalendarEventQueueDetails(props) {
|
function CalendarEventQueueDetails(props) {
|
||||||
const {
|
const {
|
||||||
@@ -13,6 +12,7 @@ function CalendarEventQueueDetails(props) {
|
|||||||
status,
|
status,
|
||||||
trackedDownloadState,
|
trackedDownloadState,
|
||||||
trackedDownloadStatus,
|
trackedDownloadStatus,
|
||||||
|
statusMessages,
|
||||||
errorMessage
|
errorMessage
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@@ -27,16 +27,15 @@ function CalendarEventQueueDetails(props) {
|
|||||||
status={status}
|
status={status}
|
||||||
trackedDownloadState={trackedDownloadState}
|
trackedDownloadState={trackedDownloadState}
|
||||||
trackedDownloadStatus={trackedDownloadStatus}
|
trackedDownloadStatus={trackedDownloadStatus}
|
||||||
|
statusMessages={statusMessages}
|
||||||
errorMessage={errorMessage}
|
errorMessage={errorMessage}
|
||||||
progressBar={
|
progressBar={
|
||||||
<div title={translate('MovieIsDownloadingInterp', [progress.toFixed(1), title])}>
|
<CircularProgressBar
|
||||||
<CircularProgressBar
|
progress={progress}
|
||||||
progress={progress}
|
size={20}
|
||||||
size={20}
|
strokeWidth={2}
|
||||||
strokeWidth={2}
|
strokeColor={'#7a43b6'}
|
||||||
strokeColor={'#7a43b6'}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -50,6 +49,7 @@ CalendarEventQueueDetails.propTypes = {
|
|||||||
status: PropTypes.string.isRequired,
|
status: PropTypes.string.isRequired,
|
||||||
trackedDownloadState: PropTypes.string.isRequired,
|
trackedDownloadState: PropTypes.string.isRequired,
|
||||||
trackedDownloadStatus: PropTypes.string.isRequired,
|
trackedDownloadStatus: PropTypes.string.isRequired,
|
||||||
|
statusMessages: PropTypes.arrayOf(PropTypes.object),
|
||||||
errorMessage: PropTypes.string
|
errorMessage: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,18 +8,21 @@ import styles from './Legend.css';
|
|||||||
|
|
||||||
function Legend(props) {
|
function Legend(props) {
|
||||||
const {
|
const {
|
||||||
|
view,
|
||||||
showCutoffUnmetIcon,
|
showCutoffUnmetIcon,
|
||||||
|
fullColorEvents,
|
||||||
colorImpairedMode
|
colorImpairedMode
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const iconsToShow = [];
|
const iconsToShow = [];
|
||||||
|
const isAgendaView = view === 'agenda';
|
||||||
|
|
||||||
if (showCutoffUnmetIcon) {
|
if (showCutoffUnmetIcon) {
|
||||||
iconsToShow.push(
|
iconsToShow.push(
|
||||||
<LegendIconItem
|
<LegendIconItem
|
||||||
name={translate('CutoffUnmet')}
|
name={translate('CutoffUnmet')}
|
||||||
icon={icons.MOVIE_FILE}
|
icon={icons.MOVIE_FILE}
|
||||||
kind={kinds.WARNING}
|
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
|
||||||
tooltip={translate('QualityOrLangCutoffHasNotBeenMet')}
|
tooltip={translate('QualityOrLangCutoffHasNotBeenMet')}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -29,45 +32,58 @@ function Legend(props) {
|
|||||||
<div className={styles.legend}>
|
<div className={styles.legend}>
|
||||||
<div>
|
<div>
|
||||||
<LegendItem
|
<LegendItem
|
||||||
style='ended'
|
status="downloaded"
|
||||||
name={translate('DownloadedAndMonitored')}
|
name={translate('DownloadedAndMonitored')}
|
||||||
|
isAgendaView={isAgendaView}
|
||||||
|
fullColorEvents={fullColorEvents}
|
||||||
colorImpairedMode={colorImpairedMode}
|
colorImpairedMode={colorImpairedMode}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LegendItem
|
<LegendItem
|
||||||
style='availNotMonitored'
|
status="unmonitored"
|
||||||
name={translate('DownloadedButNotMonitored')}
|
name={translate('DownloadedButNotMonitored')}
|
||||||
|
isAgendaView={isAgendaView}
|
||||||
|
fullColorEvents={fullColorEvents}
|
||||||
colorImpairedMode={colorImpairedMode}
|
colorImpairedMode={colorImpairedMode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<LegendItem
|
<LegendItem
|
||||||
style='missingMonitored'
|
status="missingMonitored"
|
||||||
name={translate('MissingMonitoredAndConsideredAvailable')}
|
name={translate('MissingMonitoredAndConsideredAvailable')}
|
||||||
|
isAgendaView={isAgendaView}
|
||||||
|
fullColorEvents={fullColorEvents}
|
||||||
colorImpairedMode={colorImpairedMode}
|
colorImpairedMode={colorImpairedMode}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LegendItem
|
<LegendItem
|
||||||
style='missingUnmonitored'
|
status="missingUnmonitored"
|
||||||
name={translate('MissingNotMonitored')}
|
name={translate('MissingNotMonitored')}
|
||||||
|
isAgendaView={isAgendaView}
|
||||||
|
fullColorEvents={fullColorEvents}
|
||||||
colorImpairedMode={colorImpairedMode}
|
colorImpairedMode={colorImpairedMode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<LegendItem
|
<LegendItem
|
||||||
style='queue'
|
status="queue"
|
||||||
name={translate('Queued')}
|
name={translate('Queued')}
|
||||||
|
isAgendaView={isAgendaView}
|
||||||
|
fullColorEvents={fullColorEvents}
|
||||||
colorImpairedMode={colorImpairedMode}
|
colorImpairedMode={colorImpairedMode}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LegendItem
|
<LegendItem
|
||||||
style='continuing'
|
status="continuing"
|
||||||
name={translate('Unreleased')}
|
name={translate('Unreleased')}
|
||||||
|
isAgendaView={isAgendaView}
|
||||||
|
fullColorEvents={fullColorEvents}
|
||||||
colorImpairedMode={colorImpairedMode}
|
colorImpairedMode={colorImpairedMode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
iconsToShow.length > 0 &&
|
iconsToShow.length > 0 &&
|
||||||
<div>
|
<div>
|
||||||
@@ -79,7 +95,9 @@ function Legend(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Legend.propTypes = {
|
Legend.propTypes = {
|
||||||
|
view: PropTypes.string.isRequired,
|
||||||
showCutoffUnmetIcon: PropTypes.bool.isRequired,
|
showCutoffUnmetIcon: PropTypes.bool.isRequired,
|
||||||
|
fullColorEvents: PropTypes.bool.isRequired,
|
||||||
colorImpairedMode: PropTypes.bool.isRequired
|
colorImpairedMode: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,12 @@ import Legend from './Legend';
|
|||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.calendar.options,
|
(state) => state.calendar.options,
|
||||||
|
(state) => state.calendar.view,
|
||||||
createUISettingsSelector(),
|
createUISettingsSelector(),
|
||||||
(calendarOptions, uiSettings) => {
|
(calendarOptions, view, uiSettings) => {
|
||||||
return {
|
return {
|
||||||
...calendarOptions,
|
...calendarOptions,
|
||||||
|
view,
|
||||||
colorImpairedMode: uiSettings.enableColorImpairedMode
|
colorImpairedMode: uiSettings.enableColorImpairedMode
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ function LegendIconItem(props) {
|
|||||||
name,
|
name,
|
||||||
icon,
|
icon,
|
||||||
kind,
|
kind,
|
||||||
|
darken,
|
||||||
tooltip
|
tooltip
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ function LegendIconItem(props) {
|
|||||||
<Icon
|
<Icon
|
||||||
className={styles.icon}
|
className={styles.icon}
|
||||||
name={icon}
|
name={icon}
|
||||||
|
darken={darken}
|
||||||
kind={kind}
|
kind={kind}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -31,7 +33,12 @@ LegendIconItem.propTypes = {
|
|||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
icon: PropTypes.object.isRequired,
|
icon: PropTypes.object.isRequired,
|
||||||
kind: PropTypes.string.isRequired,
|
kind: PropTypes.string.isRequired,
|
||||||
|
darken: PropTypes.bool.isRequired,
|
||||||
tooltip: PropTypes.string.isRequired
|
tooltip: PropTypes.string.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
LegendIconItem.defaultProps = {
|
||||||
|
darken: false
|
||||||
|
};
|
||||||
|
|
||||||
export default LegendIconItem;
|
export default LegendIconItem;
|
||||||
|
|||||||
@@ -1,74 +1,37 @@
|
|||||||
.legendItemContainer {
|
|
||||||
margin-right: 5px;
|
|
||||||
width: 220px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legendItem {
|
.legendItem {
|
||||||
display: inline-flex;
|
margin: 3px 0;
|
||||||
margin-top: -1px;
|
margin-right: 6px;
|
||||||
vertical-align: middle;
|
padding-left: 5px;
|
||||||
line-height: 16px;
|
width: 220px;
|
||||||
|
border-left-width: 4px;
|
||||||
|
border-left-style: solid;
|
||||||
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
.legendItemColor {
|
/*
|
||||||
margin-right: 8px;
|
* Status
|
||||||
width: 30px;
|
*/
|
||||||
height: 16px;
|
|
||||||
border-radius: 4px;
|
.downloaded {
|
||||||
|
composes: downloaded from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.queue {
|
.queue {
|
||||||
composes: legendItemColor;
|
composes: queue from '~Calendar/Events/CalendarEvent.css';
|
||||||
|
|
||||||
background-color: var(--queueColor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.continuing {
|
.unmonitored {
|
||||||
composes: legendItemColor;
|
composes: unmonitored from '~Calendar/Events/CalendarEvent.css';
|
||||||
|
|
||||||
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 {
|
.missingUnmonitored {
|
||||||
composes: legendItemColor;
|
composes: missingUnmonitored from '~Calendar/Events/CalendarEvent.css';
|
||||||
|
|
||||||
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 {
|
.missingMonitored {
|
||||||
background: repeating-linear-gradient(90deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px);
|
composes: missingMonitored from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.missingUnmonitoredColorImpaired {
|
.continuing {
|
||||||
background: repeating-linear-gradient(45deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px);
|
composes: continuing from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
|
||||||
|
|
||||||
.legendItemText {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,13 @@
|
|||||||
// This file is automatically generated.
|
// This file is automatically generated.
|
||||||
// Please do not change this file!
|
// Please do not change this file!
|
||||||
interface CssExports {
|
interface CssExports {
|
||||||
'availNotMonitored': string;
|
|
||||||
'continuing': string;
|
'continuing': string;
|
||||||
'ended': string;
|
'downloaded': string;
|
||||||
'legendItem': string;
|
'legendItem': string;
|
||||||
'legendItemColor': string;
|
|
||||||
'legendItemContainer': string;
|
|
||||||
'legendItemText': string;
|
|
||||||
'missingMonitored': string;
|
'missingMonitored': string;
|
||||||
'missingMonitoredColorImpaired': string;
|
|
||||||
'missingUnmonitored': string;
|
'missingUnmonitored': string;
|
||||||
'missingUnmonitoredColorImpaired': string;
|
|
||||||
'queue': string;
|
'queue': string;
|
||||||
|
'unmonitored': string;
|
||||||
}
|
}
|
||||||
export const cssExports: CssExports;
|
export const cssExports: CssExports;
|
||||||
export default cssExports;
|
export default cssExports;
|
||||||
|
|||||||
@@ -6,29 +6,31 @@ import styles from './LegendItem.css';
|
|||||||
function LegendItem(props) {
|
function LegendItem(props) {
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
style,
|
status,
|
||||||
|
isAgendaView,
|
||||||
|
fullColorEvents,
|
||||||
colorImpairedMode
|
colorImpairedMode
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.legendItemContainer}>
|
<div
|
||||||
<div
|
className={classNames(
|
||||||
className={classNames(
|
styles.legendItem,
|
||||||
styles.legendItem,
|
styles[status],
|
||||||
styles[style],
|
colorImpairedMode && 'colorImpaired',
|
||||||
colorImpairedMode && 'colorImpaired'
|
fullColorEvents && !isAgendaView && 'fullColor'
|
||||||
)}
|
)}
|
||||||
/>
|
>
|
||||||
<div className={classNames(styles.legendItemText, colorImpairedMode && styles[`${style}ColorImpaired`])}>
|
{name}
|
||||||
{name}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
LegendItem.propTypes = {
|
LegendItem.propTypes = {
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
style: PropTypes.string.isRequired,
|
status: PropTypes.string.isRequired,
|
||||||
|
isAgendaView: PropTypes.bool.isRequired,
|
||||||
|
fullColorEvents: PropTypes.bool.isRequired,
|
||||||
colorImpairedMode: PropTypes.bool.isRequired
|
colorImpairedMode: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -26,14 +26,16 @@ class CalendarOptionsModalContent extends Component {
|
|||||||
firstDayOfWeek,
|
firstDayOfWeek,
|
||||||
calendarWeekColumnHeader,
|
calendarWeekColumnHeader,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
enableColorImpairedMode
|
enableColorImpairedMode,
|
||||||
|
fullColorEvents
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
firstDayOfWeek,
|
firstDayOfWeek,
|
||||||
calendarWeekColumnHeader,
|
calendarWeekColumnHeader,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
enableColorImpairedMode
|
enableColorImpairedMode,
|
||||||
|
fullColorEvents
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +96,7 @@ class CalendarOptionsModalContent extends Component {
|
|||||||
const {
|
const {
|
||||||
showMovieInformation,
|
showMovieInformation,
|
||||||
showCutoffUnmetIcon,
|
showCutoffUnmetIcon,
|
||||||
|
fullColorEvents,
|
||||||
onModalClose
|
onModalClose
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@@ -136,6 +139,18 @@ class CalendarOptionsModalContent extends Component {
|
|||||||
onChange={this.onOptionInputChange}
|
onChange={this.onOptionInputChange}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('FullColorEvents')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.CHECK}
|
||||||
|
name="fullColorEvents"
|
||||||
|
value={fullColorEvents}
|
||||||
|
helpText={translate('FullColorEventsHelpText')}
|
||||||
|
onChange={this.onOptionInputChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
</Form>
|
</Form>
|
||||||
</FieldSet>
|
</FieldSet>
|
||||||
|
|
||||||
@@ -176,7 +191,9 @@ class CalendarOptionsModalContent extends Component {
|
|||||||
value={timeFormat}
|
value={timeFormat}
|
||||||
onChange={this.onGlobalInputChange}
|
onChange={this.onGlobalInputChange}
|
||||||
/>
|
/>
|
||||||
</FormGroup><FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
<FormLabel>{translate('EnableColorImpairedMode')}</FormLabel>
|
<FormLabel>{translate('EnableColorImpairedMode')}</FormLabel>
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
@@ -187,7 +204,6 @@ class CalendarOptionsModalContent extends Component {
|
|||||||
onChange={this.onGlobalInputChange}
|
onChange={this.onGlobalInputChange}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
</Form>
|
</Form>
|
||||||
</FieldSet>
|
</FieldSet>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
@@ -209,6 +225,7 @@ CalendarOptionsModalContent.propTypes = {
|
|||||||
calendarWeekColumnHeader: PropTypes.string.isRequired,
|
calendarWeekColumnHeader: PropTypes.string.isRequired,
|
||||||
timeFormat: PropTypes.string.isRequired,
|
timeFormat: PropTypes.string.isRequired,
|
||||||
enableColorImpairedMode: PropTypes.bool.isRequired,
|
enableColorImpairedMode: PropTypes.bool.isRequired,
|
||||||
|
fullColorEvents: PropTypes.bool.isRequired,
|
||||||
dispatchSetCalendarOption: PropTypes.func.isRequired,
|
dispatchSetCalendarOption: PropTypes.func.isRequired,
|
||||||
dispatchSaveUISettings: PropTypes.func.isRequired,
|
dispatchSaveUISettings: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ function createMapStateToProps() {
|
|||||||
qualityProfileId: collection.qualityProfileId,
|
qualityProfileId: collection.qualityProfileId,
|
||||||
minimumAvailability: collection.minimumAvailability,
|
minimumAvailability: collection.minimumAvailability,
|
||||||
searchForMovie: collection.searchOnAdd,
|
searchForMovie: collection.searchOnAdd,
|
||||||
tags: []
|
tags: collection.tags || []
|
||||||
};
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -2,17 +2,12 @@ import PropTypes from 'prop-types';
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import createAllMoviesSelector from 'Store/Selectors/createAllMoviesSelector';
|
|
||||||
import createCollectionSelector from 'Store/Selectors/createCollectionSelector';
|
import createCollectionSelector from 'Store/Selectors/createCollectionSelector';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createCollectionSelector(),
|
createCollectionSelector(),
|
||||||
createAllMoviesSelector(),
|
(collection) => {
|
||||||
(
|
|
||||||
collection,
|
|
||||||
allMovies
|
|
||||||
) => {
|
|
||||||
// If a movie is deleted this selector may fire before the parent
|
// If a movie is deleted this selector may fire before the parent
|
||||||
// selecors, which will result in an undefined movie, if that happens
|
// 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
|
// we want to return early here and again in the render function to avoid
|
||||||
@@ -22,21 +17,11 @@ function createMapStateToProps() {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
let allGenres = [];
|
const allGenres = collection.movies.flatMap((movie) => movie.genres);
|
||||||
let libraryMovies = 0;
|
|
||||||
|
|
||||||
collection.movies.forEach((movie) => {
|
|
||||||
allGenres = allGenres.concat(movie.genres);
|
|
||||||
|
|
||||||
if (allMovies.find((libraryMovie) => libraryMovie.tmdbId === movie.tmdbId)) {
|
|
||||||
libraryMovies++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...collection,
|
...collection,
|
||||||
genres: Array.from(new Set(allGenres)).slice(0, 3),
|
genres: Array.from(new Set(allGenres)).slice(0, 3)
|
||||||
missingMovies: collection.movies.length - libraryMovies
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ class EditCollectionModalContent extends Component {
|
|||||||
minimumAvailability,
|
minimumAvailability,
|
||||||
// Id,
|
// Id,
|
||||||
rootFolderPath,
|
rootFolderPath,
|
||||||
|
tags,
|
||||||
searchOnAdd
|
searchOnAdd
|
||||||
} = item;
|
} = item;
|
||||||
|
|
||||||
@@ -126,6 +127,17 @@ class EditCollectionModalContent extends Component {
|
|||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('Tags')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.TAG}
|
||||||
|
name="tags"
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...tags}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>{translate('SearchOnAdd')}</FormLabel>
|
<FormLabel>{translate('SearchOnAdd')}</FormLabel>
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ function createMapStateToProps() {
|
|||||||
qualityProfileId: collection.qualityProfileId,
|
qualityProfileId: collection.qualityProfileId,
|
||||||
minimumAvailability: collection.minimumAvailability,
|
minimumAvailability: collection.minimumAvailability,
|
||||||
rootFolderPath: collection.rootFolderPath,
|
rootFolderPath: collection.rootFolderPath,
|
||||||
|
tags: collection.tags,
|
||||||
searchOnAdd: collection.searchOnAdd
|
searchOnAdd: collection.searchOnAdd
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,14 @@ function CollectionSortMenu(props) {
|
|||||||
>
|
>
|
||||||
{translate('Title')}
|
{translate('Title')}
|
||||||
</SortMenuItem>
|
</SortMenuItem>
|
||||||
|
<SortMenuItem
|
||||||
|
name="missingMovies"
|
||||||
|
sortKey={sortKey}
|
||||||
|
sortDirection={sortDirection}
|
||||||
|
onPress={onSortSelect}
|
||||||
|
>
|
||||||
|
{translate('Missing')}
|
||||||
|
</SortMenuItem>
|
||||||
</MenuContent>
|
</MenuContent>
|
||||||
</SortMenu>
|
</SortMenu>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ $hoverScale: 1.05;
|
|||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
display: block;
|
display: block;
|
||||||
background-color: var(--defaultColor);
|
background-color: var(--black);
|
||||||
}
|
}
|
||||||
|
|
||||||
.monitorToggleButton {
|
.monitorToggleButton {
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ CollectionOverviews.propTypes = {
|
|||||||
sortKey: PropTypes.string,
|
sortKey: PropTypes.string,
|
||||||
overviewOptions: PropTypes.object.isRequired,
|
overviewOptions: PropTypes.object.isRequired,
|
||||||
jumpToCharacter: PropTypes.string,
|
jumpToCharacter: PropTypes.string,
|
||||||
scrollTop: PropTypes.number.isRequired,
|
scrollTop: PropTypes.number,
|
||||||
scroller: PropTypes.instanceOf(Element).isRequired,
|
scroller: PropTypes.instanceOf(Element).isRequired,
|
||||||
showRelativeDates: PropTypes.bool.isRequired,
|
showRelativeDates: PropTypes.bool.isRequired,
|
||||||
shortDateFormat: PropTypes.string.isRequired,
|
shortDateFormat: PropTypes.string.isRequired,
|
||||||
|
|||||||
@@ -14,9 +14,24 @@ import { inputTypes } from 'Helpers/Props';
|
|||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
|
|
||||||
const posterSizeOptions = [
|
const posterSizeOptions = [
|
||||||
{ key: 'small', value: translate('Small') },
|
{
|
||||||
{ key: 'medium', value: translate('Medium') },
|
key: 'small',
|
||||||
{ key: 'large', value: translate('Large') }
|
get value() {
|
||||||
|
return translate('Small');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'medium',
|
||||||
|
get value() {
|
||||||
|
return translate('Medium');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'large',
|
||||||
|
get value() {
|
||||||
|
return translate('Large');
|
||||||
|
}
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
class CollectionOverviewOptionsModalContent extends Component {
|
class CollectionOverviewOptionsModalContent extends Component {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ class DescriptionListItem extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
|
className,
|
||||||
titleClassName,
|
titleClassName,
|
||||||
descriptionClassName,
|
descriptionClassName,
|
||||||
title,
|
title,
|
||||||
@@ -17,7 +18,7 @@ class DescriptionListItem extends Component {
|
|||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={className}>
|
||||||
<DescriptionListItemTitle
|
<DescriptionListItemTitle
|
||||||
className={titleClassName}
|
className={titleClassName}
|
||||||
>
|
>
|
||||||
@@ -35,6 +36,7 @@ class DescriptionListItem extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DescriptionListItem.propTypes = {
|
DescriptionListItem.propTypes = {
|
||||||
|
className: PropTypes.string,
|
||||||
titleClassName: PropTypes.string,
|
titleClassName: PropTypes.string,
|
||||||
descriptionClassName: PropTypes.string,
|
descriptionClassName: PropTypes.string,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ import styles from './FileBrowserModalContent.css';
|
|||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
name: 'type',
|
name: 'type',
|
||||||
label: translate('Type'),
|
label: () => translate('Type'),
|
||||||
isVisible: true
|
isVisible: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'name',
|
name: 'name',
|
||||||
label: translate('Name'),
|
label: () => translate('Name'),
|
||||||
isVisible: true
|
isVisible: true
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -10,12 +10,42 @@ import { NAME } from './FilterBuilderRowValue';
|
|||||||
import styles from './DateFilterBuilderRowValue.css';
|
import styles from './DateFilterBuilderRowValue.css';
|
||||||
|
|
||||||
const timeOptions = [
|
const timeOptions = [
|
||||||
{ key: 'seconds', value: translate('Seconds') },
|
{
|
||||||
{ key: 'minutes', value: translate('Minutes') },
|
key: 'seconds',
|
||||||
{ key: 'hours', value: translate('Hours') },
|
get value() {
|
||||||
{ key: 'days', value: translate('Days') },
|
return translate('Seconds');
|
||||||
{ key: 'weeks', value: translate('Weeks') },
|
}
|
||||||
{ key: 'months', value: translate('Months') }
|
},
|
||||||
|
{
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
function isInFilter(filterType) {
|
function isInFilter(filterType) {
|
||||||
|
|||||||
@@ -210,11 +210,13 @@ class FilterBuilderRow extends Component {
|
|||||||
const selectedFilterBuilderProp = this.selectedFilterBuilderProp;
|
const selectedFilterBuilderProp = this.selectedFilterBuilderProp;
|
||||||
|
|
||||||
const keyOptions = filterBuilderProps.map((availablePropFilter) => {
|
const keyOptions = filterBuilderProps.map((availablePropFilter) => {
|
||||||
|
const { name, label } = availablePropFilter;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: availablePropFilter.name,
|
key: name,
|
||||||
value: availablePropFilter.label
|
value: typeof label === 'function' ? label() : label
|
||||||
};
|
};
|
||||||
});
|
}).sort((a, b) => a.value.localeCompare(b.value));
|
||||||
|
|
||||||
const ValueComponent = getRowValueConnector(selectedFilterBuilderProp);
|
const ValueComponent = getRowValueConnector(selectedFilterBuilderProp);
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import TagInputTag from 'Components/Form/TagInputTag';
|
import TagInputTag from 'Components/Form/TagInputTag';
|
||||||
import { kinds } from 'Helpers/Props';
|
import { kinds } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
import styles from './FilterBuilderRowValueTag.css';
|
import styles from './FilterBuilderRowValueTag.css';
|
||||||
|
|
||||||
function FilterBuilderRowValueTag(props) {
|
function FilterBuilderRowValueTag(props) {
|
||||||
@@ -15,10 +16,11 @@ function FilterBuilderRowValueTag(props) {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{
|
{
|
||||||
!props.isLastTag &&
|
props.isLastTag ?
|
||||||
<span className={styles.or}>
|
null :
|
||||||
or
|
<div className={styles.or}>
|
||||||
</span>
|
{translate('Or')}
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,9 +3,24 @@ import translate from 'Utilities/String/translate';
|
|||||||
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
||||||
|
|
||||||
const protocols = [
|
const protocols = [
|
||||||
{ id: 'announced', name: translate('Announced') },
|
{
|
||||||
{ id: 'inCinemas', name: translate('InCinemas') },
|
id: 'announced',
|
||||||
{ id: 'released', name: translate('Released') }
|
get name() {
|
||||||
|
return translate('Announced');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'inCinemas',
|
||||||
|
get name() {
|
||||||
|
return translate('InCinemas');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'released',
|
||||||
|
get name() {
|
||||||
|
return translate('Released');
|
||||||
|
}
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
function MinimumAvailabilityFilterBuilderRowValue(props) {
|
function MinimumAvailabilityFilterBuilderRowValue(props) {
|
||||||
|
|||||||
@@ -4,10 +4,30 @@ import FilterBuilderRowValue from './FilterBuilderRowValue';
|
|||||||
|
|
||||||
const protocols = [
|
const protocols = [
|
||||||
{ id: 'tba', name: 'TBA' },
|
{ id: 'tba', name: 'TBA' },
|
||||||
{ id: 'announced', name: translate('Announced') },
|
{
|
||||||
{ id: 'inCinemas', name: translate('InCinemas') },
|
id: 'announced',
|
||||||
{ id: 'released', name: translate('Released') },
|
get name() {
|
||||||
{ id: 'deleted', name: translate('Deleted') }
|
return translate('Announced');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'inCinemas',
|
||||||
|
get name() {
|
||||||
|
return translate('InCinemas');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'released',
|
||||||
|
get name() {
|
||||||
|
return translate('Released');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'deleted',
|
||||||
|
get name() {
|
||||||
|
return translate('Deleted');
|
||||||
|
}
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
function ReleaseStatusFilterBuilderRowValue(props) {
|
function ReleaseStatusFilterBuilderRowValue(props) {
|
||||||
|
|||||||
@@ -4,9 +4,24 @@ import translate from 'Utilities/String/translate';
|
|||||||
import EnhancedSelectInput from './EnhancedSelectInput';
|
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||||
|
|
||||||
const availabilityOptions = [
|
const availabilityOptions = [
|
||||||
{ key: 'announced', value: translate('Announced') },
|
{
|
||||||
{ key: 'inCinemas', value: translate('InCinemas') },
|
key: 'announced',
|
||||||
{ key: 'released', value: translate('Released') }
|
get value() {
|
||||||
|
return translate('Announced');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'inCinemas',
|
||||||
|
get value() {
|
||||||
|
return translate('InCinemas');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'released',
|
||||||
|
get value() {
|
||||||
|
return translate('Released');
|
||||||
|
}
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
function AvailabilitySelectInput(props) {
|
function AvailabilitySelectInput(props) {
|
||||||
@@ -20,7 +35,7 @@ function AvailabilitySelectInput(props) {
|
|||||||
if (includeNoChange) {
|
if (includeNoChange) {
|
||||||
values.unshift({
|
values.unshift({
|
||||||
key: 'noChange',
|
key: 'noChange',
|
||||||
value: 'No Change',
|
value: translate('NoChange'),
|
||||||
disabled: true
|
disabled: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -580,7 +580,7 @@ EnhancedSelectInput.propTypes = {
|
|||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
disabledClassName: PropTypes.string,
|
disabledClassName: PropTypes.string,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.arrayOf(PropTypes.number)]).isRequired,
|
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.arrayOf(PropTypes.string), PropTypes.arrayOf(PropTypes.number)]).isRequired,
|
||||||
values: PropTypes.arrayOf(PropTypes.object).isRequired,
|
values: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
isDisabled: PropTypes.bool.isRequired,
|
isDisabled: PropTypes.bool.isRequired,
|
||||||
isFetching: PropTypes.bool.isRequired,
|
isFetching: PropTypes.bool.isRequired,
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--inputHoverBackgroundColor);
|
background-color: var(--inputHoverBackgroundColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.isDisabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.optionCheck {
|
.optionCheck {
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import DownloadClientSelectInputConnector from './DownloadClientSelectInputConne
|
|||||||
import EnhancedSelectInput from './EnhancedSelectInput';
|
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||||
import EnhancedSelectInputConnector from './EnhancedSelectInputConnector';
|
import EnhancedSelectInputConnector from './EnhancedSelectInputConnector';
|
||||||
import FormInputHelpText from './FormInputHelpText';
|
import FormInputHelpText from './FormInputHelpText';
|
||||||
import IndexerFlagsSelectInputConnector from './IndexerFlagsSelectInputConnector';
|
import IndexerFlagsSelectInput from './IndexerFlagsSelectInput';
|
||||||
|
import IndexerSelectInputConnector from './IndexerSelectInputConnector';
|
||||||
import KeyValueListInput from './KeyValueListInput';
|
import KeyValueListInput from './KeyValueListInput';
|
||||||
import LanguageSelectInputConnector from './LanguageSelectInputConnector';
|
import LanguageSelectInputConnector from './LanguageSelectInputConnector';
|
||||||
import MovieMonitoredSelectInput from './MovieMonitoredSelectInput';
|
import MovieMonitoredSelectInput from './MovieMonitoredSelectInput';
|
||||||
@@ -65,6 +66,9 @@ function getComponent(type) {
|
|||||||
case inputTypes.QUALITY_PROFILE_SELECT:
|
case inputTypes.QUALITY_PROFILE_SELECT:
|
||||||
return QualityProfileSelectInputConnector;
|
return QualityProfileSelectInputConnector;
|
||||||
|
|
||||||
|
case inputTypes.INDEXER_SELECT:
|
||||||
|
return IndexerSelectInputConnector;
|
||||||
|
|
||||||
case inputTypes.MOVIE_MONITORED_SELECT:
|
case inputTypes.MOVIE_MONITORED_SELECT:
|
||||||
return MovieMonitoredSelectInput;
|
return MovieMonitoredSelectInput;
|
||||||
|
|
||||||
@@ -72,7 +76,7 @@ function getComponent(type) {
|
|||||||
return RootFolderSelectInputConnector;
|
return RootFolderSelectInputConnector;
|
||||||
|
|
||||||
case inputTypes.INDEXER_FLAGS_SELECT:
|
case inputTypes.INDEXER_FLAGS_SELECT:
|
||||||
return IndexerFlagsSelectInputConnector;
|
return IndexerFlagsSelectInput;
|
||||||
|
|
||||||
case inputTypes.DOWNLOAD_CLIENT_SELECT:
|
case inputTypes.DOWNLOAD_CLIENT_SELECT:
|
||||||
return DownloadClientSelectInputConnector;
|
return DownloadClientSelectInputConnector;
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import AppState from 'App/State/AppState';
|
||||||
|
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||||
|
|
||||||
|
interface IndexerFlagsSelectInputProps {
|
||||||
|
name: string;
|
||||||
|
indexerFlags: number;
|
||||||
|
onChange(payload: object): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectIndexerFlagsValues = (selectedFlags: number) =>
|
||||||
|
createSelector(
|
||||||
|
(state: AppState) => state.settings.indexerFlags,
|
||||||
|
(indexerFlags) => {
|
||||||
|
const value = indexerFlags.items
|
||||||
|
.filter(
|
||||||
|
// eslint-disable-next-line no-bitwise
|
||||||
|
(item) => (selectedFlags & item.id) === item.id
|
||||||
|
)
|
||||||
|
.map(({ id }) => id);
|
||||||
|
|
||||||
|
const values = indexerFlags.items.map(({ id, name }) => ({
|
||||||
|
key: id,
|
||||||
|
value: name,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
value,
|
||||||
|
values,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function IndexerFlagsSelectInput(props: IndexerFlagsSelectInputProps) {
|
||||||
|
const { indexerFlags, onChange } = props;
|
||||||
|
|
||||||
|
const { value, values } = useSelector(selectIndexerFlagsValues(indexerFlags));
|
||||||
|
|
||||||
|
const onChangeWrapper = useCallback(
|
||||||
|
({ name, value }: { name: string; value: number[] }) => {
|
||||||
|
const indexerFlags = value.reduce((acc, flagId) => acc + flagId, 0);
|
||||||
|
|
||||||
|
onChange({ name, value: indexerFlags });
|
||||||
|
},
|
||||||
|
[onChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EnhancedSelectInput
|
||||||
|
{...props}
|
||||||
|
value={value}
|
||||||
|
values={values}
|
||||||
|
onChange={onChangeWrapper}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IndexerFlagsSelectInput;
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import EnhancedSelectInput from './EnhancedSelectInput';
|
|
||||||
|
|
||||||
function createMapStateToProps() {
|
|
||||||
return createSelector(
|
|
||||||
(state, { indexerFlags }) => indexerFlags,
|
|
||||||
(state) => state.settings.indexerFlags,
|
|
||||||
(selectedFlags, indexerFlags) => {
|
|
||||||
const value = [];
|
|
||||||
|
|
||||||
indexerFlags.items.forEach((item) => {
|
|
||||||
// eslint-disable-next-line no-bitwise
|
|
||||||
if ((selectedFlags & item.id) === item.id) {
|
|
||||||
value.push(item.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const values = indexerFlags.items.map(({ id, name }) => {
|
|
||||||
return {
|
|
||||||
key: id,
|
|
||||||
value: name
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
value,
|
|
||||||
values
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class IndexerFlagsSelectInputConnector extends Component {
|
|
||||||
|
|
||||||
onChange = ({ name, value }) => {
|
|
||||||
let indexerFlags = 0;
|
|
||||||
|
|
||||||
value.forEach((flagId) => {
|
|
||||||
indexerFlags += flagId;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.props.onChange({ name, value: indexerFlags });
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
|
|
||||||
return (
|
|
||||||
<EnhancedSelectInput
|
|
||||||
{...this.props}
|
|
||||||
onChange={this.onChange}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IndexerFlagsSelectInputConnector.propTypes = {
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
indexerFlags: PropTypes.number.isRequired,
|
|
||||||
value: PropTypes.arrayOf(PropTypes.number).isRequired,
|
|
||||||
values: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
onChange: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps)(IndexerFlagsSelectInputConnector);
|
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { fetchIndexers } from 'Store/Actions/settingsActions';
|
||||||
|
import sortByName from 'Utilities/Array/sortByName';
|
||||||
|
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
(state) => state.settings.indexers,
|
||||||
|
(state, { includeAny }) => includeAny,
|
||||||
|
(indexers, includeAny) => {
|
||||||
|
const {
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
error,
|
||||||
|
items
|
||||||
|
} = indexers;
|
||||||
|
|
||||||
|
const values = items.sort(sortByName).map((indexer) => ({
|
||||||
|
key: indexer.id,
|
||||||
|
value: indexer.name
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (includeAny) {
|
||||||
|
values.unshift({
|
||||||
|
key: 0,
|
||||||
|
value: '(Any)'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
error,
|
||||||
|
values
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
dispatchFetchIndexers: fetchIndexers
|
||||||
|
};
|
||||||
|
|
||||||
|
class IndexerSelectInputConnector extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
if (!this.props.isPopulated) {
|
||||||
|
this.props.dispatchFetchIndexers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onChange = ({ name, value }) => {
|
||||||
|
this.props.onChange({ name, value: parseInt(value) });
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<EnhancedSelectInput
|
||||||
|
{...this.props}
|
||||||
|
onChange={this.onChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IndexerSelectInputConnector.propTypes = {
|
||||||
|
isFetching: PropTypes.bool.isRequired,
|
||||||
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
|
||||||
|
values: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
includeAny: PropTypes.bool.isRequired,
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
dispatchFetchIndexers: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
IndexerSelectInputConnector.defaultProps = {
|
||||||
|
includeAny: false
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps, mapDispatchToProps)(IndexerSelectInputConnector);
|
||||||