mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-06 13:31:28 -05:00
Compare commits
283 Commits
v3.2.0.504
...
v4.0.3.587
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72b3caa72d | ||
|
|
589368781b | ||
|
|
8fd6101121 | ||
|
|
ac9d6cbf0a | ||
|
|
6e0ed36e9f | ||
|
|
fcb65055ef | ||
|
|
90456bbfed | ||
|
|
2a74b7b2e1 | ||
|
|
fc08c39fb8 | ||
|
|
76d65bf990 | ||
|
|
de243991dd | ||
|
|
4d1f251c1f | ||
|
|
ebb1e3131a | ||
|
|
6e502d63c2 | ||
|
|
57e05b70da | ||
|
|
59186adbfc | ||
|
|
bc20e159ba | ||
|
|
39b99341cd | ||
|
|
b626c5bbf0 | ||
|
|
a33b861cec | ||
|
|
3a48f07702 | ||
|
|
1aec0b7ee5 | ||
|
|
3e32161791 | ||
|
|
fda1ad237b | ||
|
|
52b6f39026 | ||
|
|
100fd95dd9 | ||
|
|
d571c7b75a | ||
|
|
8d7f48739b | ||
|
|
c061d7cec8 | ||
|
|
91691205db | ||
|
|
c1e07b30d7 | ||
|
|
78a7770858 | ||
|
|
599f4907f4 | ||
|
|
ec9a7f5c8e | ||
|
|
54c914d48f | ||
|
|
75270d8151 | ||
|
|
7a859f340b | ||
|
|
13e44ce19a | ||
|
|
9e4c94592d | ||
|
|
9d2a59b7fd | ||
|
|
194e0f3d7f | ||
|
|
d1fa92bc6c | ||
|
|
974e44ce48 | ||
|
|
de05be62d7 | ||
|
|
cae5badee0 | ||
|
|
45d8227654 | ||
|
|
7bbd2246c4 | ||
|
|
59fed13442 | ||
|
|
50b273acae | ||
|
|
4278415fd7 | ||
|
|
124b50288d | ||
|
|
3fcc395964 | ||
|
|
0ee9981cba | ||
|
|
2848899206 | ||
|
|
f1a00764cd | ||
|
|
346236764c | ||
|
|
eecd4e4b7d | ||
|
|
2838d8ca29 | ||
|
|
4ebcbc28aa | ||
|
|
2c24f7ca04 | ||
|
|
ec86de78d2 | ||
|
|
4f5f9ff77e | ||
|
|
465bb403a9 | ||
|
|
9e175e28ef | ||
|
|
4d2a311e40 | ||
|
|
b2195148a2 | ||
|
|
2ae7371d73 | ||
|
|
7b03a856c9 | ||
|
|
48d1d47b67 | ||
|
|
906b9bb92a | ||
|
|
6fc14278e6 | ||
|
|
34b269086d | ||
|
|
6c40a27f2e | ||
|
|
be158a09b4 | ||
|
|
eecd746f51 | ||
|
|
5ed034320e | ||
|
|
41dd678dfd | ||
|
|
716eadc551 | ||
|
|
1cb31aa95c | ||
|
|
568dd2fbb2 | ||
|
|
5d091e519e | ||
|
|
faab78c00a | ||
|
|
f1461056ce | ||
|
|
2042ffce62 | ||
|
|
c6ae6f7b1c | ||
|
|
8d7affae68 | ||
|
|
a418111245 | ||
|
|
6359ed5757 | ||
|
|
9a8c1d7d1b | ||
|
|
e89c2ee9f7 | ||
|
|
3d36f88939 | ||
|
|
8e4320a93b | ||
|
|
b095676010 | ||
|
|
41d69d8484 | ||
|
|
073e59e3db | ||
|
|
588a0843a4 | ||
|
|
26cedfd47d | ||
|
|
16789e5b6b | ||
|
|
159edcde94 | ||
|
|
5855773842 | ||
|
|
73f2da72f3 | ||
|
|
7dda481824 | ||
|
|
4d6c3369c6 | ||
|
|
5c7756b575 | ||
|
|
cf8aa09615 | ||
|
|
cdde7d4d8b | ||
|
|
e139708bb2 | ||
|
|
47206dd2bd | ||
|
|
9442666493 | ||
|
|
005ad00caf | ||
|
|
2ae056e727 | ||
|
|
c538424229 | ||
|
|
507e8ec814 | ||
|
|
9d6614b14a | ||
|
|
f9dab9d780 | ||
|
|
18e0656d21 | ||
|
|
759d14cf99 | ||
|
|
1238f60a5e | ||
|
|
2d28828e5e | ||
|
|
9a395b52ac | ||
|
|
e9dffb4819 | ||
|
|
8b93038937 | ||
|
|
c4cf38255e | ||
|
|
1c0621af0a | ||
|
|
dd80a64560 | ||
|
|
beb22844c9 | ||
|
|
399f242f87 | ||
|
|
6befbec381 | ||
|
|
8b8f79d6c3 | ||
|
|
e54d4765dd | ||
|
|
4068cfcabb | ||
|
|
c5b736e422 | ||
|
|
025634cd19 | ||
|
|
9c86c20c00 | ||
|
|
7bf44e2771 | ||
|
|
b18daebc8a | ||
|
|
183d3d0872 | ||
|
|
f1de24ccc8 | ||
|
|
498d9086b5 | ||
|
|
2737937d37 | ||
|
|
3c9e818933 | ||
|
|
a8b563de7b | ||
|
|
823fe2261e | ||
|
|
329d141128 | ||
|
|
d33bed6a36 | ||
|
|
3acc6a3f9d | ||
|
|
8d0a26e284 | ||
|
|
f0f8a4ffaf | ||
|
|
e7ff13085e | ||
|
|
6a8f6dc5f7 | ||
|
|
13c03d9958 | ||
|
|
5ce1829709 | ||
|
|
86da4e87ea | ||
|
|
a219b4a1b8 | ||
|
|
84dd10f032 | ||
|
|
f844cbecaf | ||
|
|
279692f9b1 | ||
|
|
6fcbdc5ba3 | ||
|
|
750b0331ab | ||
|
|
4bf311d820 | ||
|
|
67b1fd9bc5 | ||
|
|
ea8a5dc25e | ||
|
|
ae5c7a3ace | ||
|
|
4b06f6b506 | ||
|
|
147cfe538a | ||
|
|
5fb6b44950 | ||
|
|
bb4c1d6181 | ||
|
|
a774ccf426 | ||
|
|
3654340e0f | ||
|
|
ee84321d4b | ||
|
|
aa49358b97 | ||
|
|
aca669defe | ||
|
|
8cad9600cc | ||
|
|
d6967a786f | ||
|
|
06e2d5b3c3 | ||
|
|
2d53ec24f8 | ||
|
|
c14ef7bee7 | ||
|
|
4299799967 | ||
|
|
c93c87de30 | ||
|
|
64045b0810 | ||
|
|
4e1aa5b946 | ||
|
|
3ce83d0cb4 | ||
|
|
f5b0e78c2f | ||
|
|
af872e4bc5 | ||
|
|
f8a82dbb90 | ||
|
|
27c5a30cc1 | ||
|
|
0541041b2b | ||
|
|
90cff01fe5 | ||
|
|
b627c6badd | ||
|
|
4f8b0dd5cc | ||
|
|
672b37c319 | ||
|
|
d0fbcffa42 | ||
|
|
8f4b028abe | ||
|
|
db7babc6ed | ||
|
|
88d516a6d4 | ||
|
|
d091458a8d | ||
|
|
c8c9db1452 | ||
|
|
10f37e0774 | ||
|
|
49583f8507 | ||
|
|
6da18c8a4f | ||
|
|
d666366deb | ||
|
|
31ac40d2cc | ||
|
|
2d0271978d | ||
|
|
13224f03cc | ||
|
|
8024f4658c | ||
|
|
beca2c99ca | ||
|
|
94b481a66f | ||
|
|
425772da1c | ||
|
|
6808d5388c | ||
|
|
e912e14cbb | ||
|
|
6e60e3dfa4 | ||
|
|
ac9dfc4f25 | ||
|
|
0369ad86a3 | ||
|
|
8d1a9f98af | ||
|
|
34c545d932 | ||
|
|
5a51225286 | ||
|
|
d2d81db8b3 | ||
|
|
90cc79b6bc | ||
|
|
ebf4425beb | ||
|
|
0daa978fba | ||
|
|
31ba45cb7a | ||
|
|
8dad6cc8db | ||
|
|
e632dea99a | ||
|
|
560f12122f | ||
|
|
3ec5d1ef3d | ||
|
|
203d735a85 | ||
|
|
35e6d54409 | ||
|
|
2c5b4c6217 | ||
|
|
94c685a5ca | ||
|
|
28e9d112c8 | ||
|
|
4698dca813 | ||
|
|
8dbf9471a6 | ||
|
|
0ef7f0155a | ||
|
|
6f0a2de505 | ||
|
|
c23eea21fc | ||
|
|
b955a37a1c | ||
|
|
df08385603 | ||
|
|
64cf11bc54 | ||
|
|
92c4c50e1c | ||
|
|
cc572729ff | ||
|
|
79a10fa18f | ||
|
|
e3b5efc9e5 | ||
|
|
ad77a438f8 | ||
|
|
a7088ce387 | ||
|
|
b06ec1f291 | ||
|
|
8eedae1af0 | ||
|
|
a3242b4823 | ||
|
|
a037a8dbe2 | ||
|
|
eb76dd5248 | ||
|
|
143067621c | ||
|
|
84d1a8983b | ||
|
|
61b71a206d | ||
|
|
ee9ff25afc | ||
|
|
5e3e8feb0a | ||
|
|
1b63a9ad80 | ||
|
|
15a99ab650 | ||
|
|
c228361654 | ||
|
|
7d644aa544 | ||
|
|
9ef0906da1 | ||
|
|
8fd6c72037 | ||
|
|
45c08db1ce | ||
|
|
7d7b2c9e2b | ||
|
|
13ce3fc6da | ||
|
|
f55f6e198a | ||
|
|
4f68cb85e1 | ||
|
|
b7f3791966 | ||
|
|
ee8dfe1ea9 | ||
|
|
6f0d5a0583 | ||
|
|
ba1637087e | ||
|
|
bdc7733faf | ||
|
|
7f4be53db0 | ||
|
|
27d998d6f2 | ||
|
|
5eb593f453 | ||
|
|
4652db0583 | ||
|
|
35d43480bf | ||
|
|
3e7c136a7f | ||
|
|
05f9f6b413 | ||
|
|
67f6eb544a | ||
|
|
3c11e934a8 | ||
|
|
627a39b8fc | ||
|
|
95c7b96dff | ||
|
|
dadd59fc3a | ||
|
|
e67d3d3666 |
37
.github/ISSUE_TEMPLATE/bug_report.md
vendored
37
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,37 +0,0 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Support Requests will be closed immediately, if you are not 100% certain this is a bug please go to our Reddit or Discord first. Exceptions do not mean you found a bug!
|
||||
title: ''
|
||||
labels: 'Type: Bug'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
<!-- Support Requests will be closed immediately, if you are unsure go to our Reddit or Discord first. Exceptions do not mean you found a bug! -->
|
||||
**Describe the bug**
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
**To Reproduce**
|
||||
<!-- Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error -->
|
||||
|
||||
**Expected behavior**
|
||||
<!-- A clear and concise description of what you expected to happen.-->
|
||||
|
||||
**Screenshots**
|
||||
<!-- If applicable, add screenshots to help explain your problem.-->
|
||||
|
||||
**Platform Information (please complete the following information):**
|
||||
- OS: <!-- [e.g. Windows 10 2004 / Ubuntu 20.04] -->
|
||||
- Docker: <!-- [Yes/No] -->
|
||||
- Mono or .NET Version (System -> Status): <!--[e.g. Mono 5.8 or .Net Core 3.1.10 or .NET 5.0.1] -->
|
||||
- Browser and Version (Only needed for UI issues): <!--[e.g. chrome 86.0.4240.198] -->
|
||||
- Radarr Version: <!--[e.g. 3.0.1.4259, 3.0.2.4369]-->
|
||||
- Radarr Branch: <!--[e.g. master, develop]-->
|
||||
|
||||
**Trace Logs**
|
||||
Turn on Trace logs under Settings -> General and wait for the bug to occur again.
|
||||
**Upload the full log file here (or another site (e.g. pastebin) and link it). Issues will be closed, if they do not include this!**
|
||||
<!-- Trace logs are named Radarr.trace.txt or Radarr.trace.#.txt and will contain "trace" in them-->
|
||||
73
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
73
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
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'
|
||||
labels: ['Type: Bug', 'Status: Needs Triage']
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue already exists for the bug you encountered.
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Current Behavior
|
||||
description: A concise description of what you're experiencing.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: A concise description of what you expected to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
description: Steps to reproduce the behavior.
|
||||
placeholder: |
|
||||
1. In this environment...
|
||||
2. With this config...
|
||||
3. Run '...'
|
||||
4. See error...
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Environment
|
||||
description: |
|
||||
examples:
|
||||
- **OS**: Ubuntu 20.04
|
||||
- **Radarr**: Radarr 3.0.1.4259
|
||||
- **Docker Install**: Yes
|
||||
- **Using Reverse Proxy**: No
|
||||
- **Browser**: Firefox 90 (If UI related)
|
||||
value: |
|
||||
- OS:
|
||||
- Radarr:
|
||||
- Docker Install:
|
||||
- Using Reverse Proxy:
|
||||
- Browser:
|
||||
render: markdown
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: What branch are you running?
|
||||
options:
|
||||
- Master
|
||||
- Develop
|
||||
- Nightly
|
||||
- Other (This issue will be closed)
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Trace Logs?
|
||||
description: |
|
||||
Trace Logs (https://wiki.servarr.com/radarr/troubleshooting#logging-and-log-files)
|
||||
***Generally speaking, all bug reports must have trace logs provided.***
|
||||
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||
Additionally, any additional info? Screenshots? References? Anything that will give us more context about the issue you are encountering!
|
||||
validations:
|
||||
required: true
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: feature request
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||
|
||||
**Describe the solution you'd like**
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
**Additional context**
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
38
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
38
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: Feature Request
|
||||
description: 'Suggest an idea for Radarr'
|
||||
labels: ['Type: Feature Request', 'Status: Needs Triage']
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue already exists for the feature you are requesting.
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Is your feature request related to a problem? Please describe
|
||||
description: A clear and concise description of what the problem is.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the solution you'd like
|
||||
description: A clear and concise description of what you want to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe alternatives you've considered
|
||||
description: A clear and concise description of any alternative solutions or features you've considered.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: |
|
||||
Links? References? Mockups? Anything that will give us more context about the feature you are encountering!
|
||||
|
||||
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||
validations:
|
||||
required: true
|
||||
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,15 +1,16 @@
|
||||
#### Database Migration
|
||||
YES | NO
|
||||
YES - XXXX | NO
|
||||
|
||||
#### Description
|
||||
A few sentences describing the overall goals of the pull request's commits.
|
||||
|
||||
#### Screenshot (if UI related)
|
||||
|
||||
#### Todos
|
||||
- [ ] Tests
|
||||
- [ ] Translation Keys
|
||||
- [ ] Wiki Updates
|
||||
- [ ] Translation Keys (./src/NzbDrone.Core/Localization/Core/en.json)
|
||||
- [ ] [Wiki Updates](https://wiki.servarr.com)
|
||||
|
||||
#### Issues Fixed or Closed by this PR
|
||||
|
||||
* Fixes #XXXX
|
||||
* Fixes #XXXX
|
||||
3
.github/stale.yml
vendored
3
.github/stale.yml
vendored
@@ -4,7 +4,8 @@ daysUntilStale: 60
|
||||
daysUntilClose: 7
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- feature request
|
||||
- feature request #legacy
|
||||
- 'Type: Feature Request'
|
||||
- 'Status: Confirmed'
|
||||
- sonarr-pull
|
||||
- lidarr-pull
|
||||
|
||||
41
.github/workflows/azuresync.yml
vendored
Normal file
41
.github/workflows/azuresync.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: Sync issue to Azure DevOps work item
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
[opened, edited, deleted, closed, reopened, labeled, unlabeled, assigned]
|
||||
|
||||
concurrency: azuresync-${{ github.event.issue.number }}
|
||||
|
||||
jobs:
|
||||
alert:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: danhellem/github-actions-issue-to-work-item@master
|
||||
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == true }}"
|
||||
env:
|
||||
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
|
||||
github_token: "${{ github.token }}"
|
||||
ado_organization: "Servarr"
|
||||
ado_project: "Servarr"
|
||||
ado_area_path: "Servarr\\Radarr"
|
||||
ado_wit: "Bug"
|
||||
ado_new_state: "New"
|
||||
ado_active_state: "Active"
|
||||
ado_close_state: "Closed"
|
||||
ado_bypassrules: true
|
||||
log_level: 100
|
||||
- uses: danhellem/github-actions-issue-to-work-item@master
|
||||
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == false }}"
|
||||
env:
|
||||
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
|
||||
github_token: "${{ github.token }}"
|
||||
ado_organization: "Servarr"
|
||||
ado_project: "Servarr"
|
||||
ado_area_path: "Servarr\\Radarr"
|
||||
ado_wit: "User Story"
|
||||
ado_new_state: "New"
|
||||
ado_active_state: "Active"
|
||||
ado_close_state: "Closed"
|
||||
ado_bypassrules: true
|
||||
log_level: 100
|
||||
@@ -1,49 +1,13 @@
|
||||
# How to Contribute #
|
||||
# How to Contribute
|
||||
|
||||
We're always looking for people to help make Radarr even better, there are a number of ways to contribute.
|
||||
|
||||
## Documentation ##
|
||||
Setup guides, FAQ, the more information we have on the [wiki](https://wiki.servarr.com/Radarr) the better.
|
||||
This file has been moved to the wiki for the latest details please see the [contributing wiki page](https://wiki.servarr.com/radarr/contributing).
|
||||
|
||||
## Development ##
|
||||
## Documentation
|
||||
|
||||
### Tools required ###
|
||||
- Visual Studio 2019 or higher (https://www.visualstudio.com/vs/). The community version is free and works (https://www.visualstudio.com/downloads/).
|
||||
- HTML/Javascript editor of choice (VS Code/Sublime Text/Webstorm/Atom/etc)
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
- [NodeJS](https://nodejs.org/en/download/) (Node 12.X.X or higher)
|
||||
- [Yarn](https://yarnpkg.com/)
|
||||
- .NET Core 5.0.
|
||||
Setup guides, [FAQ](https://wiki.servarr.com/radarr/faq), the more information we have on the [wiki](https://wiki.servarr.com/radarr) the better.
|
||||
|
||||
### Getting started ###
|
||||
## Development
|
||||
|
||||
1. Fork Radarr
|
||||
2. Clone the repository into your development machine. [*info*](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository-from-github)
|
||||
3. Install the required Node Packages `yarn install`
|
||||
4. Start gulp to monitor your dev environment for any changes that need post processing using `yarn start` command.
|
||||
5. Build the project in Visual Studio, Setting startup project to `Radarr.Console` and framework to `net5.0`
|
||||
6. Debug the project in Visual Studio
|
||||
7. Open http://localhost:7878
|
||||
|
||||
### Contributing Code ###
|
||||
- If you're adding a new, already requested feature, please comment on [Github Issues](https://github.com/Radarr/Radarr/issues "Github Issues") so work is not duplicated (If you want to add something not already on there, please talk to us first)
|
||||
- Rebase from Radarr's develop branch, don't merge
|
||||
- Make meaningful commits, or squash them
|
||||
- Feel free to make a pull request before work is complete, this will let us see where its at and make comments/suggest improvements
|
||||
- Reach out to us on the discord if you have any questions
|
||||
- Add tests (unit/integration)
|
||||
- Commit with *nix line endings for consistency (We checkout Windows and commit *nix)
|
||||
- One feature/bug fix per pull request to keep things clean and easy to understand
|
||||
- Use 4 spaces instead of tabs, this is the default for VS 2019 and WebStorm (to my knowledge)
|
||||
|
||||
### Pull Requesting ###
|
||||
- Only make pull requests to develop, never master, if you make a PR to master we'll comment on it and close it
|
||||
- You're probably going to get some comments or questions from us, they will be to ensure consistency and maintainability
|
||||
- We'll try to respond to pull requests as soon as possible, if its been a day or two, please reach out to us, we may have missed it
|
||||
- Each PR should come from its own [feature branch](http://martinfowler.com/bliki/FeatureBranch.html) not develop in your fork, it should have a meaningful branch name (what is being added/fixed)
|
||||
- new-feature (Good)
|
||||
- fix-bug (Good)
|
||||
- patch (Bad)
|
||||
- develop (Bad)
|
||||
|
||||
If you have any questions about any of this, please let us know.
|
||||
See the [Wiki Page](https://wiki.servarr.com/radarr/contributing)
|
||||
|
||||
49
README.md
49
README.md
@@ -1,19 +1,21 @@
|
||||
# Radarr
|
||||
|
||||
[](https://dev.azure.com/Radarr/Radarr/_build/latest?definitionId=1&branchName=develop)
|
||||
[](https://translate.servarr.com/engage/radarr/?utm_source=widget)
|
||||
[](https://wiki.servarr.com/Radarr_Installation#Docker)
|
||||
[](https://translate.servarr.com/engage/radarr/?utm_source=widget)
|
||||
[](https://wiki.servarr.com/radarr/installation#docker)
|
||||

|
||||
[](#backers)
|
||||
[](#backers)
|
||||
[](#sponsors)
|
||||
[](#mega-sponsors)
|
||||
|
||||
Radarr is a movie collection manager for Usenet and BitTorrent users. It can monitor multiple RSS feeds for new movies and will interface with clients and indexers to grab, sort, and rename them. It can also be configured to automatically upgrade the quality of existing files in the library when a better quality format becomes available.
|
||||
Note that only one type of a given movie is supported. If you want both an 4k version and 1080p version of a given movie you will need multiple instances.
|
||||
|
||||
## Major Features Include:
|
||||
## Major Features Include
|
||||
|
||||
* Adding new movies with lots of information, such as trailers, ratings, etc.
|
||||
* Support for major platforms: Windows, Linux, macOS, Raspberry Pi, etc.
|
||||
* Can watch for better quality of the movies you have and do an automatic upgrade. *eg. from DVD to Blu-Ray*
|
||||
* Can watch for better quality of the movies you have and do an automatic upgrade. *e.g. from DVD to Blu-Ray*
|
||||
* Automatic failed download handling will try another release if one fails
|
||||
* Manual search so you can pick any release or to see why a release was not downloaded automatically
|
||||
* Full integration with SABnzbd and NZBGet
|
||||
@@ -21,57 +23,60 @@ Radarr is a movie collection manager for Usenet and BitTorrent users. It can mon
|
||||
* Automatically importing downloaded movies
|
||||
* Recognizing Special Editions, Director's Cut, etc.
|
||||
* Identifying releases with hardcoded subs
|
||||
* QBittorrent, Deluge, rTorrent, Transmission, uTorrent, and other download clients are supported
|
||||
* Full integration with Kodi, Plex (notification, library update)
|
||||
* A beautiful UI
|
||||
* Identifying releases with AKA movie names
|
||||
* SABnzbd, NZBGet, QBittorrent, Deluge, rTorrent, Transmission, uTorrent, and other download clients are supported and integrated
|
||||
* Full integration with Kodi and Plex (notifications, library updates)
|
||||
* Importing Metadata such as trailers or subtitles
|
||||
* Adding metadata such as posters and information for Kodi and others to use
|
||||
* Advanced customization for profiles, such that Radarr will always download the copy you want
|
||||
* A beautiful UI
|
||||
|
||||
## Support
|
||||
Note: GitHub Issues are for Bugs and Feature Requests Only
|
||||
|
||||
[](https://wiki.servarr.com/radarr)
|
||||
[](https://radarr.video/discord)
|
||||
[](https://www.reddit.com/r/Radarr)
|
||||
|
||||
Note: GitHub Issues are for Bugs and Feature Requests Only
|
||||
|
||||
[](https://github.com/Radarr/Radarr/issues)
|
||||
[](https://wiki.servarr.com/Radarr)
|
||||
|
||||
## Feature Requests
|
||||
|
||||
[Feature Requests](https://github.com/Radarr/Radarr/issues/new?assignees=&labels=Type%3A+Enhancement&template=feature_request.md&title=)
|
||||
|
||||
## Contributors & Developers
|
||||
|
||||
[API Documentation](https://radarr.video/docs/api/)
|
||||
|
||||
This project exists thanks to all the people who contribute. [Contribute](CONTRIBUTING.md).
|
||||
<a href="https://github.com/Radarr/Radarr/graphs/contributors"><img src="https://opencollective.com/Radarr/contributors.svg?width=890&button=false" /></a>
|
||||
This project exists thanks to all the people who contribute.
|
||||
- [Contribute (GitHub)](CONTRIBUTING.md)
|
||||
- [Contribution (Wiki Article)](https://wiki.servarr.com/radarr/contributing)
|
||||
|
||||
[](https://github.com/Radarr/Radarr/graphs/contributors)
|
||||
|
||||
## Backers
|
||||
|
||||
Thank you to all our backers! 🙏 [Become a backer](https://opencollective.com/Radarr#backer)
|
||||
|
||||
<img src="https://opencollective.com/Radarr/backers.svg?width=890"></a>
|
||||
[](https://opencollective.com/Radarr#backer)
|
||||
|
||||
## Sponsors
|
||||
|
||||
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor](https://opencollective.com/Radarr#sponsor)
|
||||
|
||||
<img src="https://opencollective.com/Radarr/sponsors.svg?width=890"></a>
|
||||
[](https://opencollective.com/Radarr#sponsor)
|
||||
|
||||
## Mega Sponsors
|
||||
|
||||
<img src="https://opencollective.com/Radarr/tiers/mega-sponsor.svg?width=890"></a>
|
||||
[](https://opencollective.com/Radarr#mega-sponsor)
|
||||
|
||||
## JetBrains
|
||||
|
||||
Thank you to [<img src="/Logo/jetbrains.svg" alt="JetBrains" width="32"> JetBrains](http://www.jetbrains.com/) for providing us with free licenses to their great tools.
|
||||
|
||||
* [<img src="/Logo/resharper.svg" alt="ReSharper" width="32"> ReSharper](http://www.jetbrains.com/resharper/)
|
||||
* [<img src="/Logo/webstorm.svg" alt="WebStorm" width="32"> WebStorm](http://www.jetbrains.com/webstorm/)
|
||||
* [<img src="/Logo/rider.svg" alt="Rider" width="32"> Rider](http://www.jetbrains.com/rider/)
|
||||
* [<img src="/Logo/webstorm.svg" alt="WebStorm" width="32"> WebStorm](http://www.jetbrains.com/webstorm/)
|
||||
* [<img src="/Logo/rider.svg" alt="Rider" width="32"> Rider](http://www.jetbrains.com/rider/)
|
||||
* [<img src="/Logo/dottrace.svg" alt="dotTrace" width="32"> dotTrace](http://www.jetbrains.com/dottrace/)
|
||||
|
||||
### License
|
||||
|
||||
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
|
||||
* Copyright 2010-2021
|
||||
* Copyright 2010-2022
|
||||
|
||||
@@ -7,13 +7,13 @@ variables:
|
||||
outputFolder: './_output'
|
||||
artifactsFolder: './_artifacts'
|
||||
testsFolder: './_tests'
|
||||
majorVersion: '3.2.0'
|
||||
majorVersion: '4.0.3'
|
||||
minorVersion: $[counter('minorVersion', 2000)]
|
||||
radarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
||||
sentryOrg: 'servarr'
|
||||
sentryUrl: 'https://sentry.servarr.com'
|
||||
dotnetVersion: '5.0.202'
|
||||
dotnetVersion: '6.0.101'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
|
||||
trigger:
|
||||
@@ -67,7 +67,7 @@ stages:
|
||||
enableAnalysis: 'true'
|
||||
Mac:
|
||||
osName: 'Mac'
|
||||
imageName: 'macos-10.14'
|
||||
imageName: 'macos-10.15'
|
||||
enableAnalysis: 'false'
|
||||
Windows:
|
||||
osName: 'Windows'
|
||||
@@ -111,27 +111,23 @@ stages:
|
||||
artifact: '$(osName)Backend'
|
||||
displayName: Publish Backend
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/net5.0/win-x64/publish'
|
||||
- publish: '$(testsFolder)/net6.0/win-x64/publish'
|
||||
artifact: WindowsCoreTests
|
||||
displayName: Publish Windows Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/net472/linux-x64/publish'
|
||||
artifact: LinuxTests
|
||||
displayName: Publish Linux Mono Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/net5.0/linux-x64/publish'
|
||||
- publish: '$(testsFolder)/net6.0/linux-x64/publish'
|
||||
artifact: LinuxCoreTests
|
||||
displayName: Publish Linux Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/net5.0/linux-musl-x64/publish'
|
||||
- publish: '$(testsFolder)/net6.0/linux-musl-x64/publish'
|
||||
artifact: LinuxMuslCoreTests
|
||||
displayName: Publish Linux Musl Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/net5.0/freebsd-x64/publish'
|
||||
- publish: '$(testsFolder)/net6.0/freebsd-x64/publish'
|
||||
artifact: FreebsdCoreTests
|
||||
displayName: Publish FreeBSD Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- publish: '$(testsFolder)/net5.0/osx-x64/publish'
|
||||
- publish: '$(testsFolder)/net6.0/osx-x64/publish'
|
||||
artifact: MacCoreTests
|
||||
displayName: Publish MacOS Test Package
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
@@ -148,7 +144,7 @@ stages:
|
||||
imageName: 'ubuntu-18.04'
|
||||
Mac:
|
||||
osName: 'Mac'
|
||||
imageName: 'macos-10.14'
|
||||
imageName: 'macos-10.15'
|
||||
Windows:
|
||||
osName: 'Windows'
|
||||
imageName: 'windows-2019'
|
||||
@@ -207,12 +203,12 @@ stages:
|
||||
- bash: ./build.sh --packages
|
||||
displayName: Create Packages
|
||||
- bash: |
|
||||
setup/inno/ISCC.exe setup/radarr.iss //DFramework=net5.0 //DRuntime=win-x86
|
||||
cp setup/output/Radarr.*windows.net5.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x86-installer.exe
|
||||
setup/inno/ISCC.exe setup/radarr.iss //DFramework=net6.0 //DRuntime=win-x86
|
||||
cp setup/output/Radarr.*windows.net6.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x86-installer.exe
|
||||
displayName: Create .NET Core Windows installer
|
||||
- bash: |
|
||||
setup/inno/ISCC.exe setup/radarr.iss //DFramework=net5.0 //DRuntime=win-x64
|
||||
cp setup/output/Radarr.*windows.net5.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x64-installer.exe
|
||||
setup/inno/ISCC.exe setup/radarr.iss //DFramework=net6.0 //DRuntime=win-x64
|
||||
cp setup/output/Radarr.*windows.net6.0.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x64-installer.exe
|
||||
displayName: Create .NET Core Windows installer
|
||||
- publish: $(Build.ArtifactStagingDirectory)
|
||||
artifact: 'WindowsInstaller'
|
||||
@@ -245,6 +241,7 @@ stages:
|
||||
- bash: ./build.sh --packages --enable-bsd
|
||||
displayName: Create Packages
|
||||
- bash: |
|
||||
find . -name "ffprobe" -exec chmod a+x {} \;
|
||||
find . -name "Radarr" -exec chmod a+x {} \;
|
||||
find . -name "Radarr.Update" -exec chmod a+x {} \;
|
||||
displayName: Set executable bits
|
||||
@@ -254,37 +251,44 @@ stages:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x64.zip'
|
||||
archiveType: 'zip'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/win-x64/net5.0
|
||||
rootFolderOrFile: $(artifactsFolder)/win-x64/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create Windows x86 Core zip
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x86.zip'
|
||||
archiveType: 'zip'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/win-x86/net5.0
|
||||
rootFolderOrFile: $(artifactsFolder)/win-x86/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create MacOS Core app
|
||||
displayName: Create MacOS x64 Core app
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-app-core-x64.zip'
|
||||
archiveType: 'zip'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/macos-app/net5.0
|
||||
rootFolderOrFile: $(artifactsFolder)/osx-x64-app/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create MacOS Core tar
|
||||
displayName: Create MacOS x64 Core tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-core-x64.tar.gz'
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/macos/net5.0
|
||||
rootFolderOrFile: $(artifactsFolder)/osx-x64/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create Linux Mono tar
|
||||
displayName: Create MacOS arm64 Core app
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux.tar.gz'
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-app-core-arm64.zip'
|
||||
archiveType: 'zip'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/osx-arm64-app/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create MacOS arm64 Core tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-core-arm64.tar.gz'
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-x64/net472
|
||||
rootFolderOrFile: $(artifactsFolder)/osx-arm64/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create Linux Core tar
|
||||
inputs:
|
||||
@@ -292,7 +296,7 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-x64/net5.0
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-x64/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create Linux Musl Core tar
|
||||
inputs:
|
||||
@@ -300,7 +304,7 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net5.0
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create ARM32 Linux Core tar
|
||||
inputs:
|
||||
@@ -308,7 +312,15 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-arm/net5.0
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-arm/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create ARM32 Linux Musl Core tar
|
||||
inputs:
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-musl-core-arm.tar.gz'
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create ARM64 Linux Core tar
|
||||
inputs:
|
||||
@@ -316,7 +328,7 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-arm64/net5.0
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-arm64/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create ARM64 Linux Musl Core tar
|
||||
inputs:
|
||||
@@ -324,7 +336,7 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/net5.0
|
||||
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/net6.0
|
||||
- task: ArchiveFiles@2
|
||||
displayName: Create FreeBSD Core Core tar
|
||||
inputs:
|
||||
@@ -332,7 +344,7 @@ stages:
|
||||
archiveType: 'tar'
|
||||
tarCompression: 'gz'
|
||||
includeRootFolder: false
|
||||
rootFolderOrFile: $(artifactsFolder)/freebsd-x64/net5.0
|
||||
rootFolderOrFile: $(artifactsFolder)/freebsd-x64/net6.0
|
||||
- publish: $(Build.ArtifactStagingDirectory)
|
||||
artifact: 'Packages'
|
||||
displayName: Publish Packages
|
||||
@@ -395,7 +407,7 @@ stages:
|
||||
osName: 'Mac'
|
||||
testName: 'MacCore'
|
||||
poolName: 'Azure Pipelines'
|
||||
imageName: 'macos-10.14'
|
||||
imageName: 'macos-10.15'
|
||||
WindowsCore:
|
||||
osName: 'Windows'
|
||||
testName: 'WindowsCore'
|
||||
@@ -429,24 +441,13 @@ stages:
|
||||
buildType: 'current'
|
||||
artifactName: '$(testName)Tests'
|
||||
targetPath: $(testsFolder)
|
||||
- bash: |
|
||||
wget https://mediaarea.net/repo/deb/repo-mediaarea_1.0-11_all.deb
|
||||
sudo dpkg -i repo-mediaarea_1.0-11_all.deb
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --allow-unauthenticated libmediainfo-dev libmediainfo0v5 mediainfo
|
||||
displayName: Install mediainfo
|
||||
condition: and(succeeded(), eq(variables['testName'], 'LinuxCore'))
|
||||
- powershell: Set-Service SCardSvr -StartupType Manual
|
||||
displayName: Enable Windows Test Service
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
||||
- bash: |
|
||||
SYMLINK=6_6_0
|
||||
MONOPREFIX=/Library/Frameworks/Mono.framework/Versions/$SYMLINK
|
||||
echo "##vso[task.setvariable variable=MONOPREFIX;]$MONOPREFIX"
|
||||
echo "##vso[task.setvariable variable=PKG_CONFIG_PATH;]$MONOPREFIX/lib/pkgconfig:$MONOPREFIX/share/pkgconfig:$PKG_CONFIG_PATH"
|
||||
echo "##vso[task.setvariable variable=PATH;]$MONOPREFIX/bin:$PATH"
|
||||
displayName: Set Mono Version
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Mac'))
|
||||
chmod a+x _tests/ffprobe
|
||||
displayName: Make ffprobe Executable
|
||||
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
|
||||
- bash: find ${TESTSFOLDER} -name "Radarr.Test.Dummy" -exec chmod a+x {} \;
|
||||
displayName: Make Test Dummy Executable
|
||||
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
|
||||
@@ -470,18 +471,6 @@ stages:
|
||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||
strategy:
|
||||
matrix:
|
||||
mono520:
|
||||
testName: 'Mono 5.20'
|
||||
artifactName: LinuxTests
|
||||
containerImage: ghcr.io/servarr/testimages:mono-5.20
|
||||
mono610:
|
||||
testName: 'Mono 6.10'
|
||||
artifactName: LinuxTests
|
||||
containerImage: ghcr.io/servarr/testimages:mono-6.10
|
||||
mono612:
|
||||
testName: 'Mono 6.12'
|
||||
artifactName: LinuxTests
|
||||
containerImage: ghcr.io/servarr/testimages:mono-6.12
|
||||
alpine:
|
||||
testName: 'Musl Net Core'
|
||||
artifactName: LinuxMuslCoreTests
|
||||
@@ -506,6 +495,9 @@ stages:
|
||||
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'))
|
||||
@@ -549,7 +541,7 @@ stages:
|
||||
MacCore:
|
||||
osName: 'Mac'
|
||||
testName: 'MacCore'
|
||||
imageName: 'macos-10.14'
|
||||
imageName: 'macos-10.15'
|
||||
pattern: 'Radarr.*.osx-core-x64.tar.gz'
|
||||
WindowsCore:
|
||||
osName: 'Windows'
|
||||
@@ -566,14 +558,6 @@ stages:
|
||||
vmImage: $(imageName)
|
||||
|
||||
steps:
|
||||
- bash: |
|
||||
SYMLINK=6_6_0
|
||||
MONOPREFIX=/Library/Frameworks/Mono.framework/Versions/$SYMLINK
|
||||
echo "##vso[task.setvariable variable=MONOPREFIX;]$MONOPREFIX"
|
||||
echo "##vso[task.setvariable variable=PKG_CONFIG_PATH;]$MONOPREFIX/lib/pkgconfig:$MONOPREFIX/share/pkgconfig:$PKG_CONFIG_PATH"
|
||||
echo "##vso[task.setvariable variable=PATH;]$MONOPREFIX/bin:$PATH"
|
||||
displayName: Set Mono Version
|
||||
condition: and(succeeded(), eq(variables['osName'], 'Mac'))
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Install .net core'
|
||||
inputs:
|
||||
@@ -665,21 +649,6 @@ stages:
|
||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||
strategy:
|
||||
matrix:
|
||||
mono520:
|
||||
testName: 'Mono 5.20'
|
||||
artifactName: LinuxTests
|
||||
containerImage: ghcr.io/servarr/testimages:mono-5.20
|
||||
pattern: 'Radarr.*.linux.tar.gz'
|
||||
mono610:
|
||||
testName: 'Mono 6.10'
|
||||
artifactName: LinuxTests
|
||||
containerImage: ghcr.io/servarr/testimages:mono-6.10
|
||||
pattern: 'Radarr.*.linux.tar.gz'
|
||||
mono612:
|
||||
testName: 'Mono 6.12'
|
||||
artifactName: LinuxTests
|
||||
containerImage: ghcr.io/servarr/testimages:mono-6.12
|
||||
pattern: 'Radarr.*.linux.tar.gz'
|
||||
alpine:
|
||||
testName: 'Musl Net Core'
|
||||
artifactName: LinuxMuslCoreTests
|
||||
@@ -747,7 +716,7 @@ stages:
|
||||
failBuild: true
|
||||
Mac:
|
||||
osName: 'Mac'
|
||||
imageName: 'macos-10.14'
|
||||
imageName: 'macos-10.15'
|
||||
pattern: 'Radarr.*.osx-core-x64.tar.gz'
|
||||
failBuild: true
|
||||
Windows:
|
||||
@@ -916,12 +885,12 @@ stages:
|
||||
projectVersion: '$(radarrVersion)'
|
||||
extraProperties: |
|
||||
sonar.exclusions=**/obj/**,**/*.dll,**/NzbDrone.Core.Test/Files/**/*,./frontend/**,**/ExternalModules/**,./src/Libraries/**
|
||||
sonar.coverage.exclusions=**/Radarr.Api.V3/**/*,**/NzbDrone.Api/**/*,**/MonoTorrent/**/*,**/Marr.Data/**/*
|
||||
sonar.coverage.exclusions=**/Radarr.Api.V3/**/*
|
||||
sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml
|
||||
sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml
|
||||
- bash: |
|
||||
./build.sh --backend -f net5.0 -r win-x64
|
||||
TEST_DIR=_tests/net5.0/win-x64/publish/ ./test.sh Windows Unit Coverage
|
||||
./build.sh --backend -f net6.0 -r win-x64
|
||||
TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage
|
||||
displayName: Coverage Unit Tests
|
||||
- task: SonarCloudAnalyze@1
|
||||
condition: eq(variables['System.PullRequest.IsFork'], 'False')
|
||||
@@ -951,7 +920,7 @@ stages:
|
||||
- job:
|
||||
displayName: Discord Notification
|
||||
pool:
|
||||
vmImage: 'windows-2019'
|
||||
vmImage: 'ubuntu-18.04'
|
||||
steps:
|
||||
- task: DownloadPipelineArtifact@2
|
||||
continueOnError: true
|
||||
@@ -961,7 +930,7 @@ stages:
|
||||
artifactName: 'WindowsAutomationScreenshots'
|
||||
targetPath: $(Build.SourcesDirectory)
|
||||
- checkout: none
|
||||
- powershell: |
|
||||
- pwsh: |
|
||||
iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/Servarr/AzureDiscordNotify/master/DiscordNotify.ps1'))
|
||||
env:
|
||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||
|
||||
62
build.sh
62
build.sh
@@ -33,7 +33,6 @@ EnableBsdSupport()
|
||||
|
||||
if grep -qv freebsd-x64 src/Directory.Build.props; then
|
||||
sed -i'' -e "s^<RuntimeIdentifiers>\(.*\)</RuntimeIdentifiers>^<RuntimeIdentifiers>\1;freebsd-x64</RuntimeIdentifiers>^g" src/Directory.Build.props
|
||||
sed -i'' -e "s^<ExcludedRuntimeFrameworkPairs>\(.*\)</ExcludedRuntimeFrameworkPairs>^<ExcludedRuntimeFrameworkPairs>\1;freebsd-x64:net472</ExcludedRuntimeFrameworkPairs>^g" src/Directory.Build.props
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -130,7 +129,7 @@ PackageLinux()
|
||||
|
||||
echo "Adding Radarr.Mono to UpdatePackage"
|
||||
cp $folder/Radarr.Mono.* $folder/Radarr.Update
|
||||
if [ "$framework" = "net5.0" ]; then
|
||||
if [ "$framework" = "net6.0" ]; then
|
||||
cp $folder/Mono.Posix.NETStandard.* $folder/Radarr.Update
|
||||
cp $folder/libMonoPosixHelper.* $folder/Radarr.Update
|
||||
fi
|
||||
@@ -141,17 +140,13 @@ PackageLinux()
|
||||
PackageMacOS()
|
||||
{
|
||||
local framework="$1"
|
||||
local runtime="$2"
|
||||
|
||||
ProgressStart "Creating MacOS Package for $framework"
|
||||
ProgressStart "Creating MacOS Package for $framework $runtime"
|
||||
|
||||
local folder=$artifactsFolder/macos/$framework/Radarr
|
||||
local folder=$artifactsFolder/$runtime/$framework/Radarr
|
||||
|
||||
PackageFiles "$folder" "$framework" "osx-x64"
|
||||
|
||||
if [ "$framework" = "net472" ]; then
|
||||
echo "Adding Startup script"
|
||||
cp macOS/Radarr $folder
|
||||
fi
|
||||
PackageFiles "$folder" "$framework" "$runtime"
|
||||
|
||||
echo "Removing Service helpers"
|
||||
rm -f $folder/ServiceUninstall.*
|
||||
@@ -162,7 +157,7 @@ PackageMacOS()
|
||||
|
||||
echo "Adding Radarr.Mono to UpdatePackage"
|
||||
cp $folder/Radarr.Mono.* $folder/Radarr.Update
|
||||
if [ "$framework" = "net5.0" ]; then
|
||||
if [ "$framework" = "net6.0" ]; then
|
||||
cp $folder/Mono.Posix.NETStandard.* $folder/Radarr.Update
|
||||
cp $folder/libMonoPosixHelper.* $folder/Radarr.Update
|
||||
fi
|
||||
@@ -173,10 +168,11 @@ PackageMacOS()
|
||||
PackageMacOSApp()
|
||||
{
|
||||
local framework="$1"
|
||||
local runtime="$2"
|
||||
|
||||
ProgressStart "Creating macOS App Package for $framework"
|
||||
ProgressStart "Creating macOS App Package for $framework $runtime"
|
||||
|
||||
local folder=$artifactsFolder/macos-app/$framework
|
||||
local folder="$artifactsFolder/$runtime-app/$framework"
|
||||
|
||||
rm -rf $folder
|
||||
mkdir -p $folder
|
||||
@@ -184,7 +180,7 @@ PackageMacOSApp()
|
||||
mkdir -p $folder/Radarr.app/Contents/MacOS
|
||||
|
||||
echo "Copying Binaries"
|
||||
cp -r $artifactsFolder/macos/$framework/Radarr/* $folder/Radarr.app/Contents/MacOS
|
||||
cp -r $artifactsFolder/$runtime/$framework/Radarr/* $folder/Radarr.app/Contents/MacOS
|
||||
|
||||
echo "Removing Update Folder"
|
||||
rm -r $folder/Radarr.app/Contents/MacOS/Radarr.Update
|
||||
@@ -231,8 +227,8 @@ Package()
|
||||
PackageWindows "$framework" "$runtime"
|
||||
;;
|
||||
osx)
|
||||
PackageMacOS "$framework"
|
||||
PackageMacOSApp "$framework"
|
||||
PackageMacOS "$framework" "$runtime"
|
||||
PackageMacOSApp "$framework" "$runtime"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@@ -332,15 +328,14 @@ then
|
||||
Build
|
||||
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
|
||||
then
|
||||
PackageTests "net5.0" "win-x64"
|
||||
PackageTests "net5.0" "win-x86"
|
||||
PackageTests "net5.0" "linux-x64"
|
||||
PackageTests "net5.0" "linux-musl-x64"
|
||||
PackageTests "net5.0" "osx-x64"
|
||||
PackageTests "net472" "linux-x64"
|
||||
PackageTests "net6.0" "win-x64"
|
||||
PackageTests "net6.0" "win-x86"
|
||||
PackageTests "net6.0" "linux-x64"
|
||||
PackageTests "net6.0" "linux-musl-x64"
|
||||
PackageTests "net6.0" "osx-x64"
|
||||
if [ "$ENABLE_BSD" = "YES" ];
|
||||
then
|
||||
PackageTests "net5.0" "freebsd-x64"
|
||||
PackageTests "net6.0" "freebsd-x64"
|
||||
fi
|
||||
else
|
||||
PackageTests "$FRAMEWORK" "$RID"
|
||||
@@ -369,18 +364,19 @@ then
|
||||
|
||||
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
|
||||
then
|
||||
Package "net5.0" "win-x64"
|
||||
Package "net5.0" "win-x86"
|
||||
Package "net5.0" "linux-x64"
|
||||
Package "net5.0" "linux-musl-x64"
|
||||
Package "net5.0" "linux-arm64"
|
||||
Package "net5.0" "linux-musl-arm64"
|
||||
Package "net5.0" "linux-arm"
|
||||
Package "net5.0" "osx-x64"
|
||||
Package "net472" "linux-x64"
|
||||
Package "net6.0" "win-x64"
|
||||
Package "net6.0" "win-x86"
|
||||
Package "net6.0" "linux-x64"
|
||||
Package "net6.0" "linux-musl-x64"
|
||||
Package "net6.0" "linux-arm64"
|
||||
Package "net6.0" "linux-musl-arm64"
|
||||
Package "net6.0" "linux-arm"
|
||||
Package "net6.0" "linux-musl-arm"
|
||||
Package "net6.0" "osx-x64"
|
||||
Package "net6.0" "osx-arm64"
|
||||
if [ "$ENABLE_BSD" = "YES" ];
|
||||
then
|
||||
Package "net5.0" "freebsd-x64"
|
||||
Package "net6.0" "freebsd-x64"
|
||||
fi
|
||||
else
|
||||
Package "$FRAMEWORK" "$RID"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const CopyPlugin = require('copy-webpack-plugin');
|
||||
const FileManagerPlugin = require('filemanager-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const LiveReloadPlugin = require('webpack-livereload-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
@@ -23,7 +23,7 @@ module.exports = (env) => {
|
||||
|
||||
const config = {
|
||||
mode: isProduction ? 'production' : 'development',
|
||||
devtool: 'source-map',
|
||||
devtool: isProduction ? 'source-map' : 'eval-source-map',
|
||||
|
||||
stats: {
|
||||
children: false
|
||||
@@ -87,46 +87,47 @@ module.exports = (env) => {
|
||||
}),
|
||||
|
||||
new HtmlWebpackPlugin({
|
||||
template: 'frontend/src/index.html',
|
||||
template: 'frontend/src/index.ejs',
|
||||
filename: 'index.html',
|
||||
publicPath: '/'
|
||||
}),
|
||||
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
// HTML
|
||||
{
|
||||
from: 'frontend/src/*.html',
|
||||
to: path.join(distFolder, '[name][ext]'),
|
||||
globOptions: {
|
||||
ignore: ['**/index.html']
|
||||
}
|
||||
},
|
||||
new FileManagerPlugin({
|
||||
events: {
|
||||
onEnd: {
|
||||
copy: [
|
||||
// HTML
|
||||
{
|
||||
source: 'frontend/src/*.html',
|
||||
destination: distFolder
|
||||
},
|
||||
|
||||
// Fonts
|
||||
{
|
||||
from: 'frontend/src/Content/Fonts/*.*',
|
||||
to: path.join(distFolder, 'Content/Fonts', '[name][ext]')
|
||||
},
|
||||
// Fonts
|
||||
{
|
||||
source: 'frontend/src/Content/Fonts/*.*',
|
||||
destination: path.join(distFolder, 'Content/Fonts')
|
||||
},
|
||||
|
||||
// Icon Images
|
||||
{
|
||||
from: 'frontend/src/Content/Images/Icons/*.*',
|
||||
to: path.join(distFolder, 'Content/Images/Icons', '[name][ext]')
|
||||
},
|
||||
// Icon Images
|
||||
{
|
||||
source: 'frontend/src/Content/Images/Icons/*.*',
|
||||
destination: path.join(distFolder, 'Content/Images/Icons')
|
||||
},
|
||||
|
||||
// Images
|
||||
{
|
||||
from: 'frontend/src/Content/Images/*.*',
|
||||
to: path.join(distFolder, 'Content/Images', '[name][ext]')
|
||||
},
|
||||
// Images
|
||||
{
|
||||
source: 'frontend/src/Content/Images/*.*',
|
||||
destination: path.join(distFolder, 'Content/Images')
|
||||
},
|
||||
|
||||
// Robots
|
||||
{
|
||||
from: 'frontend/src/Content/robots.txt',
|
||||
to: path.join(distFolder, 'Content', '[name][ext]')
|
||||
// Robots
|
||||
{
|
||||
source: 'frontend/src/Content/robots.txt',
|
||||
destination: path.join(distFolder, 'Content/robots.txt')
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}),
|
||||
|
||||
new LiveReloadPlugin()
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import * as commandNames from 'Commands/commandNames';
|
||||
import withCurrentPage from 'Components/withCurrentPage';
|
||||
import * as blacklistActions from 'Store/Actions/blacklistActions';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
||||
import Blacklist from './Blacklist';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.blacklist,
|
||||
createCommandExecutingSelector(commandNames.CLEAR_BLACKLIST),
|
||||
(blacklist, isClearingBlacklistExecuting) => {
|
||||
return {
|
||||
isClearingBlacklistExecuting,
|
||||
...blacklist
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
...blacklistActions,
|
||||
executeCommand
|
||||
};
|
||||
|
||||
class BlacklistConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
useCurrentPage,
|
||||
fetchBlacklist,
|
||||
gotoBlacklistFirstPage
|
||||
} = this.props;
|
||||
|
||||
registerPagePopulator(this.repopulate);
|
||||
|
||||
if (useCurrentPage) {
|
||||
fetchBlacklist();
|
||||
} else {
|
||||
gotoBlacklistFirstPage();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.isClearingBlacklistExecuting && !this.props.isClearingBlacklistExecuting) {
|
||||
this.props.gotoBlacklistFirstPage();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.clearBlacklist();
|
||||
unregisterPagePopulator(this.repopulate);
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
repopulate = () => {
|
||||
this.props.fetchBlacklist();
|
||||
}
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onFirstPagePress = () => {
|
||||
this.props.gotoBlacklistFirstPage();
|
||||
}
|
||||
|
||||
onPreviousPagePress = () => {
|
||||
this.props.gotoBlacklistPreviousPage();
|
||||
}
|
||||
|
||||
onNextPagePress = () => {
|
||||
this.props.gotoBlacklistNextPage();
|
||||
}
|
||||
|
||||
onLastPagePress = () => {
|
||||
this.props.gotoBlacklistLastPage();
|
||||
}
|
||||
|
||||
onPageSelect = (page) => {
|
||||
this.props.gotoBlacklistPage({ page });
|
||||
}
|
||||
|
||||
onRemoveSelected = (ids) => {
|
||||
this.props.removeBlacklistItems({ ids });
|
||||
}
|
||||
|
||||
onSortPress = (sortKey) => {
|
||||
this.props.setBlacklistSort({ sortKey });
|
||||
}
|
||||
|
||||
onTableOptionChange = (payload) => {
|
||||
this.props.setBlacklistTableOption(payload);
|
||||
|
||||
if (payload.pageSize) {
|
||||
this.props.gotoBlacklistFirstPage();
|
||||
}
|
||||
}
|
||||
|
||||
onClearBlacklistPress = () => {
|
||||
this.props.executeCommand({ name: commandNames.CLEAR_BLACKLIST });
|
||||
}
|
||||
|
||||
onTableOptionChange = (payload) => {
|
||||
this.props.setBlacklistTableOption(payload);
|
||||
|
||||
if (payload.pageSize) {
|
||||
this.props.gotoBlacklistFirstPage();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Blacklist
|
||||
onFirstPagePress={this.onFirstPagePress}
|
||||
onPreviousPagePress={this.onPreviousPagePress}
|
||||
onNextPagePress={this.onNextPagePress}
|
||||
onLastPagePress={this.onLastPagePress}
|
||||
onPageSelect={this.onPageSelect}
|
||||
onRemoveSelected={this.onRemoveSelected}
|
||||
onSortPress={this.onSortPress}
|
||||
onTableOptionChange={this.onTableOptionChange}
|
||||
onClearBlacklistPress={this.onClearBlacklistPress}
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
BlacklistConnector.propTypes = {
|
||||
useCurrentPage: PropTypes.bool.isRequired,
|
||||
isClearingBlacklistExecuting: PropTypes.bool.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
fetchBlacklist: PropTypes.func.isRequired,
|
||||
gotoBlacklistFirstPage: PropTypes.func.isRequired,
|
||||
gotoBlacklistPreviousPage: PropTypes.func.isRequired,
|
||||
gotoBlacklistNextPage: PropTypes.func.isRequired,
|
||||
gotoBlacklistLastPage: PropTypes.func.isRequired,
|
||||
gotoBlacklistPage: PropTypes.func.isRequired,
|
||||
removeBlacklistItems: PropTypes.func.isRequired,
|
||||
setBlacklistSort: PropTypes.func.isRequired,
|
||||
setBlacklistTableOption: PropTypes.func.isRequired,
|
||||
clearBlacklist: PropTypes.func.isRequired,
|
||||
executeCommand: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default withCurrentPage(
|
||||
connect(createMapStateToProps, mapDispatchToProps)(BlacklistConnector)
|
||||
);
|
||||
@@ -19,9 +19,9 @@ import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||
import removeOldSelectedState from 'Utilities/Table/removeOldSelectedState';
|
||||
import selectAll from 'Utilities/Table/selectAll';
|
||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||
import BlacklistRowConnector from './BlacklistRowConnector';
|
||||
import BlocklistRowConnector from './BlocklistRowConnector';
|
||||
|
||||
class Blacklist extends Component {
|
||||
class Blocklist extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
@@ -101,8 +101,8 @@ class Blacklist extends Component {
|
||||
columns,
|
||||
totalRecords,
|
||||
isRemoving,
|
||||
isClearingBlacklistExecuting,
|
||||
onClearBlacklistPress,
|
||||
isClearingBlocklistExecuting,
|
||||
onClearBlocklistPress,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
@@ -116,11 +116,11 @@ class Blacklist extends Component {
|
||||
const selectedIds = this.getSelectedIds();
|
||||
|
||||
return (
|
||||
<PageContent title={translate('Blacklist')}>
|
||||
<PageContent title={translate('Blocklist')}>
|
||||
<PageToolbar>
|
||||
<PageToolbarSection>
|
||||
<PageToolbarButton
|
||||
label="Remove Selected"
|
||||
label={translate('RemoveSelected')}
|
||||
iconName={icons.REMOVE}
|
||||
isDisabled={!selectedIds.length}
|
||||
isSpinning={isRemoving}
|
||||
@@ -130,8 +130,8 @@ class Blacklist extends Component {
|
||||
<PageToolbarButton
|
||||
label={translate('Clear')}
|
||||
iconName={icons.CLEAR}
|
||||
isSpinning={isClearingBlacklistExecuting}
|
||||
onPress={onClearBlacklistPress}
|
||||
isSpinning={isClearingBlocklistExecuting}
|
||||
onPress={onClearBlocklistPress}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
|
||||
@@ -157,7 +157,7 @@ class Blacklist extends Component {
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>
|
||||
{translate('UnableToLoadBlacklist')}
|
||||
{translate('UnableToLoadBlocklist')}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ class Blacklist extends Component {
|
||||
{
|
||||
items.map((item) => {
|
||||
return (
|
||||
<BlacklistRowConnector
|
||||
<BlocklistRowConnector
|
||||
key={item.id}
|
||||
isSelected={selectedState[item.id] || false}
|
||||
columns={columns}
|
||||
@@ -209,7 +209,7 @@ class Blacklist extends Component {
|
||||
isOpen={isConfirmRemoveModalOpen}
|
||||
kind={kinds.DANGER}
|
||||
title={translate('RemoveSelected')}
|
||||
message={translate('AreYouSureYouWantToRemoveTheSelectedItemsFromBlacklist')}
|
||||
message={translate('AreYouSureYouWantToRemoveTheSelectedItemsFromBlocklist')}
|
||||
confirmLabel={translate('RemoveSelected')}
|
||||
onConfirm={this.onRemoveSelectedConfirmed}
|
||||
onCancel={this.onConfirmRemoveModalClose}
|
||||
@@ -219,7 +219,7 @@ class Blacklist extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
Blacklist.propTypes = {
|
||||
Blocklist.propTypes = {
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
@@ -227,9 +227,9 @@ Blacklist.propTypes = {
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
totalRecords: PropTypes.number,
|
||||
isRemoving: PropTypes.bool.isRequired,
|
||||
isClearingBlacklistExecuting: PropTypes.bool.isRequired,
|
||||
isClearingBlocklistExecuting: PropTypes.bool.isRequired,
|
||||
onRemoveSelected: PropTypes.func.isRequired,
|
||||
onClearBlacklistPress: PropTypes.func.isRequired
|
||||
onClearBlocklistPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default Blacklist;
|
||||
export default Blocklist;
|
||||
152
frontend/src/Activity/Blocklist/BlocklistConnector.js
Normal file
152
frontend/src/Activity/Blocklist/BlocklistConnector.js
Normal file
@@ -0,0 +1,152 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import * as commandNames from 'Commands/commandNames';
|
||||
import withCurrentPage from 'Components/withCurrentPage';
|
||||
import * as blocklistActions from 'Store/Actions/blocklistActions';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
|
||||
import Blocklist from './Blocklist';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.blocklist,
|
||||
createCommandExecutingSelector(commandNames.CLEAR_BLOCKLIST),
|
||||
(blocklist, isClearingBlocklistExecuting) => {
|
||||
return {
|
||||
isClearingBlocklistExecuting,
|
||||
...blocklist
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
...blocklistActions,
|
||||
executeCommand
|
||||
};
|
||||
|
||||
class BlocklistConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
useCurrentPage,
|
||||
fetchBlocklist,
|
||||
gotoBlocklistFirstPage
|
||||
} = this.props;
|
||||
|
||||
registerPagePopulator(this.repopulate);
|
||||
|
||||
if (useCurrentPage) {
|
||||
fetchBlocklist();
|
||||
} else {
|
||||
gotoBlocklistFirstPage();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.isClearingBlocklistExecuting && !this.props.isClearingBlocklistExecuting) {
|
||||
this.props.gotoBlocklistFirstPage();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.clearBlocklist();
|
||||
unregisterPagePopulator(this.repopulate);
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
repopulate = () => {
|
||||
this.props.fetchBlocklist();
|
||||
}
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onFirstPagePress = () => {
|
||||
this.props.gotoBlocklistFirstPage();
|
||||
}
|
||||
|
||||
onPreviousPagePress = () => {
|
||||
this.props.gotoBlocklistPreviousPage();
|
||||
}
|
||||
|
||||
onNextPagePress = () => {
|
||||
this.props.gotoBlocklistNextPage();
|
||||
}
|
||||
|
||||
onLastPagePress = () => {
|
||||
this.props.gotoBlocklistLastPage();
|
||||
}
|
||||
|
||||
onPageSelect = (page) => {
|
||||
this.props.gotoBlocklistPage({ page });
|
||||
}
|
||||
|
||||
onRemoveSelected = (ids) => {
|
||||
this.props.removeBlocklistItems({ ids });
|
||||
}
|
||||
|
||||
onSortPress = (sortKey) => {
|
||||
this.props.setBlocklistSort({ sortKey });
|
||||
}
|
||||
|
||||
onTableOptionChange = (payload) => {
|
||||
this.props.setBlocklistTableOption(payload);
|
||||
|
||||
if (payload.pageSize) {
|
||||
this.props.gotoBlocklistFirstPage();
|
||||
}
|
||||
}
|
||||
|
||||
onClearBlocklistPress = () => {
|
||||
this.props.executeCommand({ name: commandNames.CLEAR_BLOCKLIST });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Blocklist
|
||||
onFirstPagePress={this.onFirstPagePress}
|
||||
onPreviousPagePress={this.onPreviousPagePress}
|
||||
onNextPagePress={this.onNextPagePress}
|
||||
onLastPagePress={this.onLastPagePress}
|
||||
onPageSelect={this.onPageSelect}
|
||||
onRemoveSelected={this.onRemoveSelected}
|
||||
onSortPress={this.onSortPress}
|
||||
onTableOptionChange={this.onTableOptionChange}
|
||||
onClearBlocklistPress={this.onClearBlocklistPress}
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
BlocklistConnector.propTypes = {
|
||||
useCurrentPage: PropTypes.bool.isRequired,
|
||||
isClearingBlocklistExecuting: PropTypes.bool.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
fetchBlocklist: PropTypes.func.isRequired,
|
||||
gotoBlocklistFirstPage: PropTypes.func.isRequired,
|
||||
gotoBlocklistPreviousPage: PropTypes.func.isRequired,
|
||||
gotoBlocklistNextPage: PropTypes.func.isRequired,
|
||||
gotoBlocklistLastPage: PropTypes.func.isRequired,
|
||||
gotoBlocklistPage: PropTypes.func.isRequired,
|
||||
removeBlocklistItems: PropTypes.func.isRequired,
|
||||
setBlocklistSort: PropTypes.func.isRequired,
|
||||
setBlocklistTableOption: PropTypes.func.isRequired,
|
||||
clearBlocklist: PropTypes.func.isRequired,
|
||||
executeCommand: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default withCurrentPage(
|
||||
connect(createMapStateToProps, mapDispatchToProps)(BlocklistConnector)
|
||||
);
|
||||
@@ -10,7 +10,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
class BlacklistDetailsModal extends Component {
|
||||
class BlocklistDetailsModal extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
@@ -78,7 +78,7 @@ class BlacklistDetailsModal extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
BlacklistDetailsModal.propTypes = {
|
||||
BlocklistDetailsModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
sourceTitle: PropTypes.string.isRequired,
|
||||
protocol: PropTypes.string.isRequired,
|
||||
@@ -87,4 +87,4 @@ BlacklistDetailsModal.propTypes = {
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default BlacklistDetailsModal;
|
||||
export default BlocklistDetailsModal;
|
||||
@@ -11,10 +11,10 @@ import MovieLanguage from 'Movie/MovieLanguage';
|
||||
import MovieQuality from 'Movie/MovieQuality';
|
||||
import MovieTitleLink from 'Movie/MovieTitleLink';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import BlacklistDetailsModal from './BlacklistDetailsModal';
|
||||
import styles from './BlacklistRow.css';
|
||||
import BlocklistDetailsModal from './BlocklistDetailsModal';
|
||||
import styles from './BlocklistRow.css';
|
||||
|
||||
class BlacklistRow extends Component {
|
||||
class BlocklistRow extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
@@ -166,7 +166,7 @@ class BlacklistRow extends Component {
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
title={translate('RemoveFromBlacklist')}
|
||||
title={translate('RemoveFromBlocklist')}
|
||||
name={icons.REMOVE}
|
||||
kind={kinds.DANGER}
|
||||
onPress={onRemovePress}
|
||||
@@ -179,7 +179,7 @@ class BlacklistRow extends Component {
|
||||
})
|
||||
}
|
||||
|
||||
<BlacklistDetailsModal
|
||||
<BlocklistDetailsModal
|
||||
isOpen={this.state.isDetailsModalOpen}
|
||||
sourceTitle={sourceTitle}
|
||||
protocol={protocol}
|
||||
@@ -193,7 +193,7 @@ class BlacklistRow extends Component {
|
||||
|
||||
}
|
||||
|
||||
BlacklistRow.propTypes = {
|
||||
BlocklistRow.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
movie: PropTypes.object.isRequired,
|
||||
sourceTitle: PropTypes.string.isRequired,
|
||||
@@ -210,4 +210,4 @@ BlacklistRow.propTypes = {
|
||||
onRemovePress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default BlacklistRow;
|
||||
export default BlocklistRow;
|
||||
@@ -1,8 +1,8 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { removeBlacklistItem } from 'Store/Actions/blacklistActions';
|
||||
import { removeBlocklistItem } from 'Store/Actions/blocklistActions';
|
||||
import createMovieSelector from 'Store/Selectors/createMovieSelector';
|
||||
import BlacklistRow from './BlacklistRow';
|
||||
import BlocklistRow from './BlocklistRow';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
@@ -18,9 +18,9 @@ function createMapStateToProps() {
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
onRemovePress() {
|
||||
dispatch(removeBlacklistItem({ id: props.id }));
|
||||
dispatch(removeBlocklistItem({ id: props.id }));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(createMapStateToProps, createMapDispatchToProps)(BlacklistRow);
|
||||
export default connect(createMapStateToProps, createMapDispatchToProps)(BlocklistRow);
|
||||
@@ -25,6 +25,7 @@ function HistoryDetails(props) {
|
||||
releaseGroup,
|
||||
nzbInfoUrl,
|
||||
downloadClient,
|
||||
downloadClientName,
|
||||
downloadId,
|
||||
age,
|
||||
ageHours,
|
||||
@@ -32,6 +33,8 @@ function HistoryDetails(props) {
|
||||
publishedDate
|
||||
} = data;
|
||||
|
||||
const downloadClientNameInfo = downloadClientName ?? downloadClient;
|
||||
|
||||
return (
|
||||
<DescriptionList>
|
||||
<DescriptionListItem
|
||||
@@ -71,11 +74,12 @@ function HistoryDetails(props) {
|
||||
}
|
||||
|
||||
{
|
||||
!!downloadClient &&
|
||||
downloadClientNameInfo ?
|
||||
<DescriptionListItem
|
||||
title={translate('DownloadClient')}
|
||||
data={downloadClient}
|
||||
/>
|
||||
data={downloadClientNameInfo}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -282,6 +282,17 @@ class Queue extends Component {
|
||||
return !!(item && item.movieId);
|
||||
})
|
||||
)}
|
||||
allPending={isConfirmRemoveModalOpen && (
|
||||
selectedIds.every((id) => {
|
||||
const item = items.find((i) => i.id === id);
|
||||
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return item.status === 'delay' || item.status === 'downloadClientUnavailable';
|
||||
})
|
||||
)}
|
||||
onRemovePress={this.onRemoveSelectedConfirmed}
|
||||
onModalClose={this.onConfirmRemoveModalClose}
|
||||
/>
|
||||
|
||||
@@ -42,14 +42,14 @@ class QueueRow extends Component {
|
||||
this.setState({ isRemoveQueueItemModalOpen: true });
|
||||
}
|
||||
|
||||
onRemoveQueueItemModalConfirmed = (blacklist) => {
|
||||
onRemoveQueueItemModalConfirmed = (blocklist) => {
|
||||
const {
|
||||
onRemoveQueueItemPress,
|
||||
onQueueRowModalOpenOrClose
|
||||
} = this.props;
|
||||
|
||||
onQueueRowModalOpenOrClose(false);
|
||||
onRemoveQueueItemPress(blacklist);
|
||||
onRemoveQueueItemPress(blocklist);
|
||||
|
||||
this.setState({ isRemoveQueueItemModalOpen: false });
|
||||
}
|
||||
@@ -332,6 +332,7 @@ class QueueRow extends Component {
|
||||
isOpen={isRemoveQueueItemModalOpen}
|
||||
sourceTitle={title}
|
||||
canIgnore={!!movie}
|
||||
isPending={isPending}
|
||||
onRemovePress={this.onRemoveQueueItemModalConfirmed}
|
||||
onModalClose={this.onRemoveQueueItemModalClose}
|
||||
/>
|
||||
|
||||
@@ -22,7 +22,7 @@ class RemoveQueueItemModal extends Component {
|
||||
|
||||
this.state = {
|
||||
remove: true,
|
||||
blacklist: false
|
||||
blocklist: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ class RemoveQueueItemModal extends Component {
|
||||
resetState = function() {
|
||||
this.setState({
|
||||
remove: true,
|
||||
blacklist: false
|
||||
blocklist: false
|
||||
});
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ class RemoveQueueItemModal extends Component {
|
||||
this.setState({ remove: value });
|
||||
}
|
||||
|
||||
onBlacklistChange = ({ value }) => {
|
||||
this.setState({ blacklist: value });
|
||||
onBlocklistChange = ({ value }) => {
|
||||
this.setState({ blocklist: value });
|
||||
}
|
||||
|
||||
onRemoveConfirmed = () => {
|
||||
@@ -66,10 +66,11 @@ class RemoveQueueItemModal extends Component {
|
||||
const {
|
||||
isOpen,
|
||||
sourceTitle,
|
||||
canIgnore
|
||||
canIgnore,
|
||||
isPending
|
||||
} = this.props;
|
||||
|
||||
const { remove, blacklist } = this.state;
|
||||
const { remove, blocklist } = this.state;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -89,27 +90,31 @@ class RemoveQueueItemModal extends Component {
|
||||
{translate('RemoveFromQueueText', [sourceTitle])}
|
||||
</div>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
|
||||
{
|
||||
isPending ?
|
||||
null :
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="remove"
|
||||
value={remove}
|
||||
helpTextWarning={translate('RemoveHelpTextWarning')}
|
||||
isDisabled={!canIgnore}
|
||||
onChange={this.onRemoveChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="remove"
|
||||
value={remove}
|
||||
helpTextWarning={translate('RemoveHelpTextWarning')}
|
||||
isDisabled={!canIgnore}
|
||||
onChange={this.onRemoveChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
}
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('BlacklistRelease')}</FormLabel>
|
||||
<FormLabel>{translate('BlocklistRelease')}</FormLabel>
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="blacklist"
|
||||
value={blacklist}
|
||||
helpText={translate('BlacklistHelpText')}
|
||||
onChange={this.onBlacklistChange}
|
||||
name="blocklist"
|
||||
value={blocklist}
|
||||
helpText={translate('BlocklistHelpText')}
|
||||
onChange={this.onBlocklistChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
@@ -137,6 +142,7 @@ RemoveQueueItemModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
sourceTitle: PropTypes.string.isRequired,
|
||||
canIgnore: PropTypes.bool.isRequired,
|
||||
isPending: PropTypes.bool.isRequired,
|
||||
onRemovePress: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -23,7 +23,7 @@ class RemoveQueueItemsModal extends Component {
|
||||
|
||||
this.state = {
|
||||
remove: true,
|
||||
blacklist: false
|
||||
blocklist: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ class RemoveQueueItemsModal extends Component {
|
||||
resetState = function() {
|
||||
this.setState({
|
||||
remove: true,
|
||||
blacklist: false
|
||||
blocklist: false
|
||||
});
|
||||
}
|
||||
|
||||
@@ -44,8 +44,8 @@ class RemoveQueueItemsModal extends Component {
|
||||
this.setState({ remove: value });
|
||||
}
|
||||
|
||||
onBlacklistChange = ({ value }) => {
|
||||
this.setState({ blacklist: value });
|
||||
onBlocklistChange = ({ value }) => {
|
||||
this.setState({ blocklist: value });
|
||||
}
|
||||
|
||||
onRemoveConfirmed = () => {
|
||||
@@ -67,10 +67,11 @@ class RemoveQueueItemsModal extends Component {
|
||||
const {
|
||||
isOpen,
|
||||
selectedCount,
|
||||
canIgnore
|
||||
canIgnore,
|
||||
allPending
|
||||
} = this.props;
|
||||
|
||||
const { remove, blacklist } = this.state;
|
||||
const { remove, blocklist } = this.state;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -82,38 +83,42 @@ class RemoveQueueItemsModal extends Component {
|
||||
onModalClose={this.onModalClose}
|
||||
>
|
||||
<ModalHeader>
|
||||
Remove Selected Item{selectedCount > 1 ? 's' : ''}
|
||||
{selectedCount > 1 ? translate('RemoveSelectedItems') : translate('RemoveSelectedItem')}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<div className={styles.message}>
|
||||
{translate('AreYouSureYouWantToRemoveSelectedItemsFromQueue', [selectedCount, selectedCount > 1 ? 's' : ''])}
|
||||
{selectedCount > 1 ? translate('AreYouSureYouWantToRemoveSelectedItemsFromQueue', selectedCount) : translate('AreYouSureYouWantToRemoveSelectedItemFromQueue')}
|
||||
</div>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
|
||||
{
|
||||
allPending ?
|
||||
null :
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="remove"
|
||||
value={remove}
|
||||
helpTextWarning={translate('RemoveHelpTextWarning')}
|
||||
isDisabled={!canIgnore}
|
||||
onChange={this.onRemoveChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="remove"
|
||||
value={remove}
|
||||
helpTextWarning={translate('RemoveHelpTextWarning')}
|
||||
isDisabled={!canIgnore}
|
||||
onChange={this.onRemoveChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
}
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>
|
||||
Blacklist Release{selectedCount > 1 ? 's' : ''}
|
||||
{selectedCount > 1 ? translate('BlocklistReleases') : translate('BlocklistRelease')}
|
||||
</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="blacklist"
|
||||
value={blacklist}
|
||||
helpText={translate('BlacklistHelpText')}
|
||||
onChange={this.onBlacklistChange}
|
||||
name="blocklist"
|
||||
value={blocklist}
|
||||
helpText={translate('BlocklistHelpText')}
|
||||
onChange={this.onBlocklistChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
@@ -141,6 +146,7 @@ RemoveQueueItemsModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
selectedCount: PropTypes.number.isRequired,
|
||||
canIgnore: PropTypes.bool.isRequired,
|
||||
allPending: PropTypes.bool.isRequired,
|
||||
onRemovePress: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -103,7 +103,7 @@ class AddNewMovie extends Component {
|
||||
className={styles.searchInput}
|
||||
name="movieLookup"
|
||||
value={term}
|
||||
placeholder="eg. The Dark Knight, tmdb:155, imdb:tt0468569"
|
||||
placeholder="e.g. The Dark Knight, tmdb:155, imdb:tt0468569"
|
||||
autoFocus={true}
|
||||
onChange={this.onSearchInputChange}
|
||||
/>
|
||||
@@ -161,7 +161,7 @@ class AddNewMovie extends Component {
|
||||
{translate('YouCanAlsoSearch')}
|
||||
</div>
|
||||
<div>
|
||||
<Link to="https://wiki.servarr.com/Radarr_FAQ#Why_cant_I_add_a_new_movie_to_Radarr">
|
||||
<Link to="https://wiki.servarr.com/radarr/faq#why-can-i-not-add-a-new-movie-to-radarr">
|
||||
{translate('CantFindMovie')}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -86,6 +86,13 @@ class AddNewMovieSearchResult extends Component {
|
||||
} = this.state;
|
||||
|
||||
const linkProps = isExistingMovie ? { to: `/movie/${titleSlug}` } : { onPress: this.onPress };
|
||||
const posterWidth = 167;
|
||||
const posterHeight = 250;
|
||||
|
||||
const elementStyle = {
|
||||
width: `${posterWidth}px`,
|
||||
height: `${posterHeight}px`
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.searchResult}>
|
||||
@@ -102,6 +109,7 @@ class AddNewMovieSearchResult extends Component {
|
||||
<div className={styles.posterContainer}>
|
||||
<MoviePoster
|
||||
className={styles.poster}
|
||||
style={elementStyle}
|
||||
images={images}
|
||||
size={250}
|
||||
overflow={true}
|
||||
@@ -114,7 +122,7 @@ class AddNewMovieSearchResult extends Component {
|
||||
monitored={monitored}
|
||||
hasFile={hasFile}
|
||||
status={status}
|
||||
posterWidth={167}
|
||||
posterWidth={posterWidth}
|
||||
detailedProgressBar={true}
|
||||
queueStatus={queueStatus}
|
||||
queueState={queueState}
|
||||
@@ -183,7 +191,7 @@ class AddNewMovieSearchResult extends Component {
|
||||
<div>
|
||||
<Label size={sizes.LARGE}>
|
||||
<HeartRating
|
||||
rating={ratings.value}
|
||||
ratings={ratings}
|
||||
iconSize={13}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
@@ -30,3 +30,9 @@
|
||||
.importButtonIcon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.addErrorAlert {
|
||||
composes: alert from '~Components/Alert.css';
|
||||
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import FileBrowserModal from 'Components/FileBrowser/FileBrowserModal';
|
||||
import Icon from 'Components/Icon';
|
||||
@@ -72,23 +73,29 @@ class ImportMovieSelectFolder extends Component {
|
||||
isWindows,
|
||||
isFetching,
|
||||
isPopulated,
|
||||
isSaving,
|
||||
error,
|
||||
saveError,
|
||||
items
|
||||
} = this.props;
|
||||
|
||||
const hasRootFolders = items.length > 0;
|
||||
|
||||
return (
|
||||
<PageContent title={translate('ImportMovies')}>
|
||||
<PageContentBody>
|
||||
{
|
||||
isFetching && !isPopulated &&
|
||||
<LoadingIndicator />
|
||||
isFetching && !isPopulated ?
|
||||
<LoadingIndicator /> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
!isFetching && error ?
|
||||
<div>
|
||||
{translate('UnableToLoadRootFolders')}
|
||||
</div>
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
@@ -108,7 +115,7 @@ class ImportMovieSelectFolder extends Component {
|
||||
</div>
|
||||
|
||||
{
|
||||
items.length > 0 ?
|
||||
hasRootFolders ?
|
||||
<div className={styles.recentFolders}>
|
||||
<FieldSet legend={translate('RecentFolders')}>
|
||||
<Table
|
||||
@@ -131,35 +138,51 @@ class ImportMovieSelectFolder extends Component {
|
||||
</TableBody>
|
||||
</Table>
|
||||
</FieldSet>
|
||||
|
||||
<Button
|
||||
kind={kinds.PRIMARY}
|
||||
size={sizes.LARGE}
|
||||
onPress={this.onAddNewRootFolderPress}
|
||||
>
|
||||
<Icon
|
||||
className={styles.importButtonIcon}
|
||||
name={icons.DRIVE}
|
||||
/>
|
||||
{translate('ChooseAnotherFolder')}
|
||||
</Button>
|
||||
</div> :
|
||||
|
||||
<div className={styles.startImport}>
|
||||
<Button
|
||||
kind={kinds.PRIMARY}
|
||||
size={sizes.LARGE}
|
||||
onPress={this.onAddNewRootFolderPress}
|
||||
>
|
||||
<Icon
|
||||
className={styles.importButtonIcon}
|
||||
name={icons.DRIVE}
|
||||
/>
|
||||
{translate('StartImport')}
|
||||
</Button>
|
||||
</div>
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
!isSaving && saveError ?
|
||||
<Alert
|
||||
className={styles.addErrorAlert}
|
||||
kind={kinds.DANGER}
|
||||
>
|
||||
{translate('UnableToAddRootFolder')}
|
||||
|
||||
<ul>
|
||||
{
|
||||
saveError.responseJSON.map((e, index) => {
|
||||
return (
|
||||
<li key={index}>
|
||||
{e.errorMessage}
|
||||
</li>
|
||||
);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</Alert> :
|
||||
null
|
||||
}
|
||||
|
||||
<div className={hasRootFolders ? undefined : styles.startImport}>
|
||||
<Button
|
||||
kind={kinds.PRIMARY}
|
||||
size={sizes.LARGE}
|
||||
onPress={this.onAddNewRootFolderPress}
|
||||
>
|
||||
<Icon
|
||||
className={styles.importButtonIcon}
|
||||
name={icons.DRIVE}
|
||||
/>
|
||||
{
|
||||
hasRootFolders ?
|
||||
translate('ChooseAnotherFolder') :
|
||||
translate('StartImport')
|
||||
}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<FileBrowserModal
|
||||
isOpen={this.state.isAddNewRootFolderModalOpen}
|
||||
name="rootFolderPath"
|
||||
@@ -179,7 +202,9 @@ ImportMovieSelectFolder.propTypes = {
|
||||
isWindows: PropTypes.bool.isRequired,
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
saveError: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onNewRootFolderSelect: PropTypes.func.isRequired,
|
||||
onDeleteRootFolderPress: PropTypes.func.isRequired
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Redirect, Route } from 'react-router-dom';
|
||||
import BlacklistConnector from 'Activity/Blacklist/BlacklistConnector';
|
||||
import BlocklistConnector from 'Activity/Blocklist/BlocklistConnector';
|
||||
import HistoryConnector from 'Activity/History/HistoryConnector';
|
||||
import QueueConnector from 'Activity/Queue/QueueConnector';
|
||||
import AddNewMovieConnector from 'AddMovie/AddNewMovie/AddNewMovieConnector';
|
||||
@@ -111,8 +111,8 @@ function AppRoutes(props) {
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/activity/blacklist"
|
||||
component={BlacklistConnector}
|
||||
path="/activity/blocklist"
|
||||
component={BlocklistConnector}
|
||||
/>
|
||||
|
||||
{/*
|
||||
|
||||
@@ -54,20 +54,24 @@
|
||||
composes: downloaded from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
.downloading {
|
||||
composes: downloading from '~Calendar/Events/CalendarEvent.css';
|
||||
.queue {
|
||||
composes: queue from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
.unmonitored {
|
||||
composes: unmonitored from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
.missing {
|
||||
composes: missing from '~Calendar/Events/CalendarEvent.css';
|
||||
.missingUnmonitored {
|
||||
composes: missingUnmonitored from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
.unreleased {
|
||||
composes: unreleased from '~Calendar/Events/CalendarEvent.css';
|
||||
.missingMonitored {
|
||||
composes: missingMonitored from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
.continuing {
|
||||
composes: continuing from '~Calendar/Events/CalendarEvent.css';
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointSmall) {
|
||||
|
||||
@@ -3,10 +3,10 @@ import moment from 'moment';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import CalendarEventQueueDetails from 'Calendar/Events/CalendarEventQueueDetails';
|
||||
import getStatusStyle from 'Calendar/getStatusStyle';
|
||||
import Icon from 'Components/Icon';
|
||||
import Link from 'Components/Link/Link';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import getStatusStyle from 'Utilities/Movie/getStatusStyle';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './AgendaEvent.css';
|
||||
|
||||
@@ -82,7 +82,7 @@ class AgendaEvent extends Component {
|
||||
startTime = moment(startTime);
|
||||
const downloading = !!(queueItem || grabbed);
|
||||
const isMonitored = monitored;
|
||||
const statusStyle = getStatusStyle(hasFile, downloading, isAvailable, isMonitored);
|
||||
const statusStyle = getStatusStyle(null, isMonitored, hasFile, isAvailable, 'style', downloading);
|
||||
const joinedGenres = genres.slice(0, 2).join(', ');
|
||||
const link = `/movie/${titleSlug}`;
|
||||
|
||||
|
||||
@@ -60,39 +60,30 @@
|
||||
}
|
||||
}
|
||||
|
||||
.downloading {
|
||||
.queue {
|
||||
border-left-color: $purple !important;
|
||||
}
|
||||
|
||||
.unmonitored {
|
||||
border-left-color: $gray !important;
|
||||
}
|
||||
|
||||
.missingUnmonitored {
|
||||
border-left-color: $warningColor !important;
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(45deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
||||
}
|
||||
}
|
||||
|
||||
.onAir {
|
||||
border-left-color: $warningColor !important;
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
||||
}
|
||||
}
|
||||
|
||||
.missing {
|
||||
.missingMonitored {
|
||||
border-left-color: $dangerColor !important;
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
border-left-color: color($dangerColor saturation(+15%)) !important;
|
||||
background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
||||
}
|
||||
}
|
||||
|
||||
.unreleased {
|
||||
.continuing {
|
||||
border-left-color: $primaryColor !important;
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@ import classNames from 'classnames';
|
||||
import moment from 'moment';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import getStatusStyle from 'Calendar/getStatusStyle';
|
||||
import Icon from 'Components/Icon';
|
||||
import Link from 'Components/Link/Link';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import getStatusStyle from 'Utilities/Movie/getStatusStyle';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import CalendarEventQueueDetails from './CalendarEventQueueDetails';
|
||||
import styles from './CalendarEvent.css';
|
||||
@@ -38,20 +38,20 @@ class CalendarEvent extends Component {
|
||||
|
||||
const isDownloading = !!(queueItem || grabbed);
|
||||
const isMonitored = monitored;
|
||||
const statusStyle = getStatusStyle(hasFile, isDownloading, isAvailable, isMonitored);
|
||||
const statusStyle = getStatusStyle(null, isMonitored, hasFile, isAvailable, 'style', isDownloading);
|
||||
const joinedGenres = genres.slice(0, 2).join(', ');
|
||||
const link = `/movie/${titleSlug}`;
|
||||
const eventType = [];
|
||||
|
||||
if (moment(date).isSame(moment(inCinemas), 'day')) {
|
||||
if (inCinemas && moment(date).isSame(moment(inCinemas), 'day')) {
|
||||
eventType.push('Cinemas');
|
||||
}
|
||||
|
||||
if (moment(date).isSame(moment(physicalRelease), 'day')) {
|
||||
if (physicalRelease && moment(date).isSame(moment(physicalRelease), 'day')) {
|
||||
eventType.push('Physical');
|
||||
}
|
||||
|
||||
if (moment(date).isSame(moment(digitalRelease), 'day')) {
|
||||
if (digitalRelease && moment(date).isSame(moment(digitalRelease), 'day')) {
|
||||
eventType.push('Digital');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
|
||||
function getStatusStyle(hasFile, downloading, isAvailable, isMonitored) {
|
||||
|
||||
if (hasFile) {
|
||||
return 'downloaded';
|
||||
}
|
||||
|
||||
if (downloading) {
|
||||
return 'downloading';
|
||||
}
|
||||
|
||||
if (!isMonitored) {
|
||||
return 'unmonitored';
|
||||
}
|
||||
|
||||
if (isAvailable && !hasFile) {
|
||||
return 'missing';
|
||||
}
|
||||
|
||||
return 'unreleased';
|
||||
}
|
||||
|
||||
export default getStatusStyle;
|
||||
@@ -1,7 +1,7 @@
|
||||
export const APPLICATION_UPDATE = 'ApplicationUpdate';
|
||||
export const BACKUP = 'Backup';
|
||||
export const REFRESH_MONITORED_DOWNLOADS = 'RefreshMonitoredDownloads';
|
||||
export const CLEAR_BLACKLIST = 'ClearBlacklist';
|
||||
export const CLEAR_BLOCKLIST = 'ClearBlocklist';
|
||||
export const CLEAR_LOGS = 'ClearLog';
|
||||
export const CUTOFF_UNMET_MOVIES_SEARCH = 'CutoffUnmetMoviesSearch';
|
||||
export const DELETE_LOG_FILES = 'DeleteLogFiles';
|
||||
|
||||
@@ -16,4 +16,9 @@
|
||||
color: #3a3f51;
|
||||
font-size: 21px;
|
||||
line-height: inherit;
|
||||
|
||||
&.small {
|
||||
color: #909293;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import styles from './FieldSet.css';
|
||||
|
||||
class FieldSet extends Component {
|
||||
@@ -9,13 +11,14 @@ class FieldSet extends Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
size,
|
||||
legend,
|
||||
children
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<fieldset className={styles.fieldSet}>
|
||||
<legend className={styles.legend}>
|
||||
<legend className={classNames(styles.legend, (size === sizes.SMALL) && styles.small)}>
|
||||
{legend}
|
||||
</legend>
|
||||
{children}
|
||||
@@ -26,8 +29,13 @@ class FieldSet extends Component {
|
||||
}
|
||||
|
||||
FieldSet.propTypes = {
|
||||
size: PropTypes.oneOf(sizes.all).isRequired,
|
||||
legend: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
|
||||
children: PropTypes.node
|
||||
};
|
||||
|
||||
FieldSet.defaultProps = {
|
||||
size: sizes.MEDIUM
|
||||
};
|
||||
|
||||
export default FieldSet;
|
||||
|
||||
@@ -129,7 +129,7 @@ class FileBrowserModalContent extends Component {
|
||||
className={styles.mappedDrivesWarning}
|
||||
kind={kinds.WARNING}
|
||||
>
|
||||
<Link to="https://wiki.servarr.com/Radarr_FAQ#Why_cant_Radarr_see_my_files_on_a_remote_server">
|
||||
<Link to="https://wiki.servarr.com/radarr/faq#why-cant-radarr-see-my-files-on-a-remote-server">
|
||||
{translate('MappedDrivesRunningAsService')}
|
||||
</Link> .
|
||||
</Alert>
|
||||
|
||||
@@ -166,7 +166,9 @@ class FilterBuilderModalContent extends Component {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.label}>Filters</div>
|
||||
<div className={styles.label}>
|
||||
{translate('Filters')}
|
||||
</div>
|
||||
|
||||
<div className={styles.rows}>
|
||||
{
|
||||
|
||||
@@ -6,8 +6,7 @@ import SelectInput from './SelectInput';
|
||||
const availabilityOptions = [
|
||||
{ key: 'announced', value: translate('Announced') },
|
||||
{ key: 'inCinemas', value: translate('InCinemas') },
|
||||
{ key: 'released', value: translate('Released') },
|
||||
{ key: 'preDB', value: translate('PreDB') }
|
||||
{ key: 'released', value: translate('Released') }
|
||||
];
|
||||
|
||||
function AvailabilitySelectInput(props) {
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { fetchDownloadClients } from 'Store/Actions/settingsActions';
|
||||
import sortByName from 'Utilities/Array/sortByName';
|
||||
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.settings.downloadClients,
|
||||
(state, { includeAny }) => includeAny,
|
||||
(state, { protocol }) => protocol,
|
||||
(downloadClients, includeAny, protocolFilter) => {
|
||||
const {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
items
|
||||
} = downloadClients;
|
||||
|
||||
const filteredItems = items.filter((item) => item.protocol === protocolFilter);
|
||||
|
||||
const values = _.map(filteredItems.sort(sortByName), (downloadClient) => {
|
||||
return {
|
||||
key: downloadClient.id,
|
||||
value: downloadClient.name
|
||||
};
|
||||
});
|
||||
|
||||
if (includeAny) {
|
||||
values.unshift({
|
||||
key: 0,
|
||||
value: '(Any)'
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
values
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
dispatchFetchDownloadClients: fetchDownloadClients
|
||||
};
|
||||
|
||||
class DownloadClientSelectInputConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
if (!this.props.isPopulated) {
|
||||
this.props.dispatchFetchDownloadClients();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onChange = ({ name, value }) => {
|
||||
this.props.onChange({ name, value: parseInt(value) });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<EnhancedSelectInput
|
||||
{...this.props}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DownloadClientSelectInputConnector.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,
|
||||
dispatchFetchDownloadClients: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
DownloadClientSelectInputConnector.defaultProps = {
|
||||
includeAny: false,
|
||||
protocol: 'torrent'
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(DownloadClientSelectInputConnector);
|
||||
@@ -8,6 +8,7 @@ import AvailabilitySelectInput from './AvailabilitySelectInput';
|
||||
import CaptchaInputConnector from './CaptchaInputConnector';
|
||||
import CheckInput from './CheckInput';
|
||||
import DeviceInputConnector from './DeviceInputConnector';
|
||||
import DownloadClientSelectInputConnector from './DownloadClientSelectInputConnector';
|
||||
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||
import EnhancedSelectInputConnector from './EnhancedSelectInputConnector';
|
||||
import FormInputHelpText from './FormInputHelpText';
|
||||
@@ -73,6 +74,9 @@ function getComponent(type) {
|
||||
case inputTypes.INDEXER_FLAGS_SELECT:
|
||||
return IndexerFlagsSelectInputConnector;
|
||||
|
||||
case inputTypes.DOWNLOAD_CLIENT_SELECT:
|
||||
return DownloadClientSelectInputConnector;
|
||||
|
||||
case inputTypes.LANGUAGE_SELECT:
|
||||
return LanguageSelectInputConnector;
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ function ProviderFieldFormGroup(props) {
|
||||
label,
|
||||
helpText,
|
||||
helpLink,
|
||||
placeholder,
|
||||
value,
|
||||
type,
|
||||
advanced,
|
||||
@@ -96,6 +97,7 @@ function ProviderFieldFormGroup(props) {
|
||||
label={label}
|
||||
helpText={helpText}
|
||||
helpLink={helpLink}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
values={getSelectValues(selectOptions)}
|
||||
errors={errors}
|
||||
@@ -121,6 +123,7 @@ ProviderFieldFormGroup.propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
helpText: PropTypes.string,
|
||||
helpLink: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
value: PropTypes.any,
|
||||
type: PropTypes.string.isRequired,
|
||||
advanced: PropTypes.bool.isRequired,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
.heart {
|
||||
.image {
|
||||
align-content: center;
|
||||
margin-right: 5px;
|
||||
color: $themeRed;
|
||||
vertical-align: -0.125em;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
5
frontend/src/Components/ImdbRating.css
Normal file
5
frontend/src/Components/ImdbRating.css
Normal file
@@ -0,0 +1,5 @@
|
||||
.image {
|
||||
align-content: center;
|
||||
margin-right: 5px;
|
||||
vertical-align: -0.125em;
|
||||
}
|
||||
57
frontend/src/Components/ImdbRating.js
Normal file
57
frontend/src/Components/ImdbRating.js
Normal file
File diff suppressed because one or more lines are too long
@@ -100,7 +100,9 @@ class PageJumpBar extends Component {
|
||||
// Listeners
|
||||
|
||||
onMeasure = ({ height }) => {
|
||||
this.setState({ height });
|
||||
if (height > 0) {
|
||||
this.setState({ height });
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -61,8 +61,8 @@ const links = [
|
||||
to: '/activity/history'
|
||||
},
|
||||
{
|
||||
title: translate('Blacklist'),
|
||||
to: '/activity/blacklist'
|
||||
title: translate('Blocklist'),
|
||||
to: '/activity/blocklist'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
background-color: $dangerColor;
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(90deg, color($dangerColor shade(5%)), color($dangerColor shade(5%)) 5px, color($dangerColor shade(15%)) 5px, color($dangerColor shade(15%)) 10px);
|
||||
background: repeating-linear-gradient(90deg, $dangerColor, $dangerColor 5px, $dangerColor 5px, $dimColor 10px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
background-color: $warningColor;
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(45deg, $warningColor, $warningColor 5px, color($warningColor tint(15%)) 5px, color($warningColor tint(15%)) 10px);
|
||||
background: repeating-linear-gradient(45deg, $warningColor, $warningColor 5px, $warningColor 5px, $dimColor 10px);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
5
frontend/src/Components/RottenTomatoRating.css
Normal file
5
frontend/src/Components/RottenTomatoRating.css
Normal file
@@ -0,0 +1,5 @@
|
||||
.image {
|
||||
align-content: center;
|
||||
margin-right: 5px;
|
||||
vertical-align: -0.125em;
|
||||
}
|
||||
58
frontend/src/Components/RottenTomatoRating.js
Normal file
58
frontend/src/Components/RottenTomatoRating.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { PureComponent } from 'react';
|
||||
import styles from './RottenTomatoRating.css';
|
||||
|
||||
class RottenTomatoRating extends PureComponent {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
|
||||
const {
|
||||
ratings,
|
||||
hideIcon,
|
||||
iconSize
|
||||
} = this.props;
|
||||
|
||||
const rtRotten = 'data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTYwIDU2MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNDQ1LjE4NSA0NDQuNjg0Yy03OS4zNjkgNC4xNjctOTUuNTg3LTg2LjY1Mi0xMjYuNzI2LTg2LjAwNi0xMy4yNjguMjc5LTIzLjcyNiAxNC4xNTEtMTkuMTMzIDMwLjMyIDIuNTI1IDguODg4IDkuNTMgMjEuOTIzIDEzLjk0NCAzMC4wMTEgMTUuNTcgMjguNTQ0LTcuNDQ3IDYwLjg0NS0zNC4zODMgNjMuNTc3LTQ0Ljc2IDQuNTQtNjMuNDMzLTIxLjQyNi02Mi4yNzgtNDguMDA3IDEuMy0yOS44NCAyNi42LTYwLjMzMS42NS03My4zMDUtMjcuMTk0LTEzLjU5Ny00OS4zMDEgMzkuNTcyLTc1LjMyNSA1MS40MzktMjMuNTUzIDEwLjc0MS01Ni4yNDggMi40MTMtNjcuODcyLTIzLjc0MS04LjE2NC0xOC4zNzktNi42OC01My43NjggMjkuNjctNjcuMjcgMjIuNzA2LTguNDMzIDczLjMwNSAxMS4wMjkgNzUuOS0xMy42MjMgMi45OTItMjguNDE2LTUzLjE1NS0zMC44MTItNzAuMDYtMzcuNjI2LTI5LjkxMi0xMi4wNTUtNDcuNTY3LTM3Ljg1LTMzLjczNC02NS41MjIgMTAuMzc4LTIwLjc1NyA0MC45MTUtMjkuMjAzIDY0LjIyMy0yMC4xMSAyNy45MjIgMTAuODkyIDMyLjQwNCAzOS44NTMgNDYuNzEgNTEuODk3IDEyLjMyNCAxMC4zOCAyOS4xOSAxMS42OCA0MC4yMiA0LjU0MyA4LjEzNS01LjI2NSAxMC44NDMtMTYuODI4IDcuNzc0LTI3LjM5LTQuMDctMTQuMDIzLTE0Ljg3NS0yMi43NzMtMjUuNDE1LTMxLjM0Ni0xOC43NTgtMTUuMjQ5LTQ1LjI0LTI4LjM2LTI5LjIyMi02OS45ODMgMTMuMTMtMzQuMTEgNTEuNjQyLTM1LjM0IDUxLjY0Mi0zNS4zNCAxNS4zLTEuNzIgMjkuMDAyIDIuOSA0MC4xNjcgMTIuODc1IDE0LjkyNyAxMy4zMzUgMTcuODM0IDMxLjE2IDE1LjMzNiA1MC4xNzYtMi4yODMgMTcuMzU4LTguNDI2IDMyLjU2LTExLjYzIDQ5Ljc1OS0zLjcxNyAxOS45NjYgNi45NTQgNDAuMDg2IDI3LjI0OSA0MC44NjkgMjYuNjk0IDEuMDMxIDM0LjY5OC0xOS40ODYgMzcuOTY0LTMyLjQ5MiA0Ljc4Mi0xOS4wMjggMTEuMDU4LTM2LjY5NCAyOC43MTgtNDcuODIgMjUuMzQ2LTE1Ljk3IDYwLjU1Mi0xMi40NyA3Ni44ODYgMTguMjIyIDEyLjkyIDI0LjI4NCA4Ljc3MiA1Ny43MTUtMTEuMDQ3IDc1Ljk3LTguODkyIDguMTg4LTE5LjU4NCAxMS4wNzUtMzEuMTQ4IDExLjE1Ni0xNi41ODUuMTE3LTMzLjE2Mi0uMjktNDguNTU2IDcuNDcxLTEwLjQ4IDUuMjgxLTE1LjA0NyAxMy44ODgtMTUuMDQ1IDI1LjQyMyAwIDExLjI0MiA1Ljg1MyAxOC41ODUgMTUuMzM2IDIzLjM2MyAxNy44NiA5LjAwMyAzNy41NzcgMTAuODQzIDU2Ljg3MSAxNC4yMjIgMjcuOTggNC45IDUyLjU4MSAxNC43NTUgNjguMzc1IDQwLjcyLjE0Mi4yMjguMjguNDU4LjQxNS42OSAxOC4xMzkgMzAuNzQxLS44MzEgNzUuMDA1LTM2LjQ3NiA3Ni44NzgiIGZpbGw9IiMwQUM4NTUiLz48L3N2Zz4=';
|
||||
const rtFresh = 'data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTYwIDU2MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtNDc4LjI5IDI5Ni45OGMtMy45OS02My45NjYtMzYuNTItMTExLjgyLTg1LjQ2OC0xMzguNTggMC4yNzggMS41Ni0xLjEwOSAzLjUwOC0yLjY4OCAyLjgxOC0zMi4wMTYtMTQuMDA2LTg2LjMyOCAzMS4zMi0xMjQuMjggNy41ODQgMC4yODUgOC41MTktMS4zNzggNTAuMDcyLTU5LjkxNCA1Mi40ODMtMS4zODIgMC4wNTYtMi4xNDItMS4zNTUtMS4yNjgtMi4zNTQgNy44MjgtOC45MjkgMTUuNzMyLTMxLjUzNSA0LjM2Ny00My41ODYtMjQuMzM4IDIxLjgxLTM4LjQ3MiAzMC4wMTctODUuMTM4IDE5LjE4Ni0yOS44NzggMzEuMjQxLTQ2LjgwOSA3NC00My40ODUgMTI3LjI2IDYuNzggMTA4Ljc0IDEwOC42MyAxNzAuODkgMjExLjE5IDE2NC40OSAxMDIuNTYtNi4zOTUgMTkzLjQ3LTgwLjU3MiAxODYuNjgtMTg5LjMxIiBmaWxsPSIjRkEzMjBBIi8+PHBhdGggZD0iTTI5MS4zNzUgMTMyLjI5M2MyMS4wNzUtNS4wMjMgODEuNjkzLS40OSAxMDEuMTE0IDI1LjI3NCAxLjE2NiAxLjU0NS0uNDc1IDQuNDY4LTIuMzU1IDMuNjQ4LTMyLjAxNi0xNC4wMDYtODYuMzI4IDMxLjMyLTEyNC4yODIgNy41ODQuMjg1IDguNTE5LTEuMzc4IDUwLjA3Mi01OS45MTQgNTIuNDgzLTEuMzgyLjA1Ni0yLjE0Mi0xLjM1NS0xLjI2OC0yLjM1NCA3LjgyOC04LjkyOSAxNS43My0zMS41MzUgNC4zNjctNDMuNTg2LTI2LjUxMiAyMy43NTgtNDAuODg0IDMxLjM5Mi05OC40MjYgMTUuODM4LTEuODgzLS41MDgtMS4yNDEtMy41MzUuNzYyLTQuMjk4IDEwLjg3Ni00LjE1NyAzNS41MTUtMjIuMzYxIDU4LjgyNC0zMC4zODUgNC40MzgtMS41MjYgOC44NjItMi43MSAxMy4xOC0zLjQtMjUuNjY1LTIuMjkzLTM3LjIzNS01Ljg2Mi01My41NTktMy40LTEuNzg5LjI3LTMuMDA0LTEuODEzLTEuODk1LTMuMjQxIDIxLjk5NS0yOC4zMzIgNjIuNTEzLTM2Ljg4OCA4Ny41MTItMjEuODM3LTE1LjQxLTE5LjA5NC0yNy40OC0zNC4zMjEtMjcuNDgtMzQuMzIxbDI4LjYwMS0xNi4yNDZzMTEuODE3IDI2LjQgMjAuNDE0IDQ1LjYxNGMyMS4yNzUtMzEuNDM1IDYwLjg2LTM0LjMzNiA3Ny41ODUtMTIuMDMzLjk5MiAxLjMyNi0uMDQ1IDMuMjEtMS43MDIgMy4xNzEtMTMuNjEyLS4zMzEtMjEuMTA3IDEyLjA1LTIxLjY3NSAyMS40NjZsLjE5Ny4wMjMiIGZpbGw9IiMwMDkxMkQiLz48L3N2Zz4=';
|
||||
|
||||
const rating = ratings.rottenTomatoes;
|
||||
|
||||
let ratingString = '0%';
|
||||
|
||||
if (rating) {
|
||||
ratingString = `${rating.value}%`;
|
||||
}
|
||||
|
||||
return (
|
||||
<span>
|
||||
{
|
||||
!hideIcon &&
|
||||
<img
|
||||
className={styles.image}
|
||||
src={rating.value > 50 ? rtFresh : rtRotten}
|
||||
style={{
|
||||
height: `${iconSize}px`
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
{ratingString}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RottenTomatoRating.propTypes = {
|
||||
ratings: PropTypes.object.isRequired,
|
||||
iconSize: PropTypes.number.isRequired,
|
||||
hideIcon: PropTypes.bool
|
||||
};
|
||||
|
||||
RottenTomatoRating.defaultProps = {
|
||||
iconSize: 14
|
||||
};
|
||||
|
||||
export default RottenTomatoRating;
|
||||
5
frontend/src/Components/TmdbRating.css
Normal file
5
frontend/src/Components/TmdbRating.css
Normal file
@@ -0,0 +1,5 @@
|
||||
.image {
|
||||
align-content: center;
|
||||
margin-right: 5px;
|
||||
vertical-align: -0.125em;
|
||||
}
|
||||
57
frontend/src/Components/TmdbRating.js
Normal file
57
frontend/src/Components/TmdbRating.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { PureComponent } from 'react';
|
||||
import styles from './TmdbRating.css';
|
||||
|
||||
class TmdbRating extends PureComponent {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
|
||||
const {
|
||||
ratings,
|
||||
hideIcon,
|
||||
iconSize
|
||||
} = this.props;
|
||||
|
||||
const tmdbImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxOTAuMjQgODEuNTIiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0iYSIgeTE9IjQwLjc2IiB4Mj0iMTkwLjI0IiB5Mj0iNDAuNzYiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM5MGNlYTEiLz48c3RvcCBvZmZzZXQ9Ii41NiIgc3RvcC1jb2xvcj0iIzNjYmVjOSIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzAwYjNlNSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxwYXRoIGQ9Ik0xMDUuNjcgMzYuMDZoNjYuOWExNy42NyAxNy42NyAwIDAwMTcuNjctMTcuNjZBMTcuNjcgMTcuNjcgMCAwMDE3Mi41Ny43M2gtNjYuOUExNy42NyAxNy42NyAwIDAwODggMTguNGExNy42NyAxNy42NyAwIDAwMTcuNjcgMTcuNjZ6bS04OCA0NWg3Ni45YTE3LjY3IDE3LjY3IDAgMDAxNy42Ny0xNy42NiAxNy42NyAxNy42NyAwIDAwLTE3LjY3LTE3LjY3aC03Ni45QTE3LjY3IDE3LjY3IDAgMDAwIDYzLjRhMTcuNjcgMTcuNjcgMCAwMDE3LjY3IDE3LjY2em0tNy4yNi00NS42NGg3LjhWNi45MmgxMC4xVjBoLTI4djYuOWgxMC4xem0yOC4xIDBoNy44VjguMjVoLjFsOSAyNy4xNWg2bDkuMy0yNy4xNWguMVYzNS40aDcuOFYwSDY2Ljc2bC04LjIgMjMuMWgtLjFMNTAuMzEgMGgtMTEuOHptMTEzLjkyIDIwLjI1YTE1LjA3IDE1LjA3IDAgMDAtNC41Mi01LjUyIDE4LjU3IDE4LjU3IDAgMDAtNi42OC0zLjA4IDMzLjU0IDMzLjU0IDAgMDAtOC4wNy0xaC0xMS43djM1LjRoMTIuNzVhMjQuNTggMjQuNTggMCAwMDcuNTUtMS4xNSAxOS4zNCAxOS4zNCAwIDAwNi4zNS0zLjMyIDE2LjI3IDE2LjI3IDAgMDA0LjM3LTUuNSAxNi45MSAxNi45MSAwIDAwMS42My03LjU4IDE4LjUgMTguNSAwIDAwLTEuNjgtOC4yNXpNMTQ1IDY4LjZhOC44IDguOCAwIDAxLTIuNjQgMy40IDEwLjcgMTAuNyAwIDAxLTQgMS44MiAyMS41NyAyMS41NyAwIDAxLTUgLjU1aC00LjA1di0yMWg0LjZhMTcgMTcgMCAwMTQuNjcuNjMgMTEuNjYgMTEuNjYgMCAwMTMuODggMS44N0E5LjE0IDkuMTQgMCAwMTE0NSA1OWE5Ljg3IDkuODcgMCAwMTEgNC41MiAxMS44OSAxMS44OSAwIDAxLTEgNS4wOHptNDQuNjMtLjEzYTggOCAwIDAwLTEuNTgtMi42MiA4LjM4IDguMzggMCAwMC0yLjQyLTEuODUgMTAuMzEgMTAuMzEgMCAwMC0zLjE3LTF2LS4xYTkuMjIgOS4yMiAwIDAwNC40Mi0yLjgyIDcuNDMgNy40MyAwIDAwMS42OC01IDguNDIgOC40MiAwIDAwLTEuMTUtNC42NSA4LjA5IDguMDkgMCAwMC0zLTIuNzIgMTIuNTYgMTIuNTYgMCAwMC00LjE4LTEuMyAzMi44NCAzMi44NCAwIDAwLTQuNjItLjMzaC0xMy4ydjM1LjRoMTQuNWEyMi40MSAyMi40MSAwIDAwNC43Mi0uNSAxMy41MyAxMy41MyAwIDAwNC4yOC0xLjY1IDkuNDIgOS40MiAwIDAwMy4xLTMgOC41MiA4LjUyIDAgMDAxLjItNC42OCA5LjM5IDkuMzkgMCAwMC0uNTUtMy4xOHptLTE5LjQyLTE1Ljc1aDUuM2ExMCAxMCAwIDAxMS44NS4xOCA2LjE4IDYuMTggMCAwMTEuNy41NyAzLjM5IDMuMzkgMCAwMTEuMjIgMS4xMyAzLjIyIDMuMjIgMCAwMS40OCAxLjgyIDMuNjMgMy42MyAwIDAxLS40MyAxLjggMy40IDMuNCAwIDAxLTEuMTIgMS4yIDQuOTIgNC45MiAwIDAxLTEuNTguNjUgNy41MSA3LjUxIDAgMDEtMS43Ny4yaC01LjY1em0xMS43MiAyMGEzLjkgMy45IDAgMDEtMS4yMiAxLjMgNC42NCA0LjY0IDAgMDEtMS42OC43IDguMTggOC4xOCAwIDAxLTEuODIuMmgtN3YtOGg1LjlhMTUuMzUgMTUuMzUgMCAwMTIgLjE1IDguNDcgOC40NyAwIDAxMi4wNS41NSA0IDQgMCAwMTEuNTcgMS4xOCAzLjExIDMuMTEgMCAwMS42MyAyIDMuNzEgMy43MSAwIDAxLS40MyAxLjkyeiIgZmlsbD0idXJsKCNhKSIvPjwvc3ZnPg==';
|
||||
|
||||
const rating = ratings.tmdb;
|
||||
|
||||
let ratingString = '0%';
|
||||
|
||||
if (rating) {
|
||||
ratingString = `${rating.value * 10}%`;
|
||||
}
|
||||
|
||||
return (
|
||||
<span title={`${rating.votes} votes`}>
|
||||
{
|
||||
!hideIcon &&
|
||||
<img
|
||||
className={styles.image}
|
||||
src={tmdbImage}
|
||||
style={{
|
||||
height: `${iconSize}px`
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
{ratingString}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TmdbRating.propTypes = {
|
||||
ratings: PropTypes.object.isRequired,
|
||||
iconSize: PropTypes.number.isRequired,
|
||||
hideIcon: PropTypes.bool
|
||||
};
|
||||
|
||||
TmdbRating.defaultProps = {
|
||||
iconSize: 14
|
||||
};
|
||||
|
||||
export default TmdbRating;
|
||||
BIN
frontend/src/Content/Images/Icons/logo-prowlarr.png
Normal file
BIN
frontend/src/Content/Images/Icons/logo-prowlarr.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
@@ -73,7 +73,7 @@ function getInfoRowProps(row, props) {
|
||||
return {
|
||||
title: translate('Ratings'),
|
||||
iconName: icons.HEART,
|
||||
label: `${props.ratings.value * 10}%`
|
||||
label: `${props.ratings.tmdb.value * 10}%`
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ function DiscoverMoviePosterInfo(props) {
|
||||
return (
|
||||
<div className={styles.info}>
|
||||
<HeartRating
|
||||
rating={ratings.value}
|
||||
ratings={ratings}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -129,7 +129,7 @@ DiscoverMoviePosterInfo.propTypes = {
|
||||
digitalRelease: PropTypes.string,
|
||||
physicalRelease: PropTypes.string,
|
||||
runtime: PropTypes.number,
|
||||
ratings: PropTypes.object,
|
||||
ratings: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
sortKey: PropTypes.string.isRequired,
|
||||
showRelativeDates: PropTypes.bool.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
|
||||
@@ -246,7 +246,7 @@ class DiscoverMovieRow extends Component {
|
||||
className={styles[name]}
|
||||
>
|
||||
<HeartRating
|
||||
rating={ratings.value}
|
||||
ratings={ratings}
|
||||
/>
|
||||
</VirtualTableRowCell>
|
||||
);
|
||||
@@ -373,7 +373,7 @@ DiscoverMovieRow.propTypes = {
|
||||
digitalRelease: PropTypes.string,
|
||||
runtime: PropTypes.number,
|
||||
genres: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
ratings: PropTypes.object.isRequired,
|
||||
ratings: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
certification: PropTypes.string,
|
||||
collection: PropTypes.object,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
|
||||
@@ -225,4 +225,4 @@ export const UNSAVED_SETTING = farDotCircle;
|
||||
export const VIEW = fasEye;
|
||||
export const WARNING = fasExclamationTriangle;
|
||||
export const WIKI = fasBookReader;
|
||||
export const BLACKLIST = fasBan;
|
||||
export const BLOCKLIST = fasBan;
|
||||
|
||||
@@ -13,6 +13,7 @@ export const QUALITY_PROFILE_SELECT = 'qualityProfileSelect';
|
||||
export const ROOT_FOLDER_SELECT = 'rootFolderSelect';
|
||||
export const INDEXER_FLAGS_SELECT = 'indexerFlagsSelect';
|
||||
export const LANGUAGE_SELECT = 'languageSelect';
|
||||
export const DOWNLOAD_CLIENT_SELECT = 'downloadClientSelect';
|
||||
export const SELECT = 'select';
|
||||
export const DYNAMIC_SELECT = 'dynamicSelect';
|
||||
export const TAG = 'tag';
|
||||
@@ -35,6 +36,7 @@ export const all = [
|
||||
PASSWORD,
|
||||
PATH,
|
||||
QUALITY_PROFILE_SELECT,
|
||||
DOWNLOAD_CLIENT_SELECT,
|
||||
ROOT_FOLDER_SELECT,
|
||||
INDEXER_FLAGS_SELECT,
|
||||
LANGUAGE_SELECT,
|
||||
|
||||
@@ -19,6 +19,7 @@ import { align, icons, kinds, scrollDirections } from 'Helpers/Props';
|
||||
import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal';
|
||||
import SelectMovieModal from 'InteractiveImport/Movie/SelectMovieModal';
|
||||
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
|
||||
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
|
||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||
@@ -40,6 +41,11 @@ const columns = [
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'releaseGroup',
|
||||
label: translate('ReleaseGroup'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'quality',
|
||||
label: translate('Quality'),
|
||||
@@ -64,6 +70,7 @@ const columns = [
|
||||
name: icons.DANGER,
|
||||
kind: kinds.DANGER
|
||||
}),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
}
|
||||
];
|
||||
@@ -82,6 +89,7 @@ const SELECT = 'select';
|
||||
const MOVIE = 'movie';
|
||||
const LANGUAGE = 'language';
|
||||
const QUALITY = 'quality';
|
||||
const RELEASE_GROUP = 'releaseGroup';
|
||||
|
||||
class InteractiveImportModalContent extends Component {
|
||||
|
||||
@@ -201,10 +209,11 @@ class InteractiveImportModalContent extends Component {
|
||||
const errorMessage = getErrorMessage(error, translate('UnableToLoadManualImportItems'));
|
||||
|
||||
const bulkSelectOptions = [
|
||||
{
|
||||
key: SELECT, value: translate('SelectDotDot'), disabled: true },
|
||||
{ key: SELECT, value: translate('SelectDotDot'), disabled: true },
|
||||
{ key: LANGUAGE, value: translate('SelectLanguage') },
|
||||
{ key: QUALITY, value: translate('SelectQuality') }];
|
||||
{ key: QUALITY, value: translate('SelectQuality') },
|
||||
{ key: RELEASE_GROUP, value: translate('SelectReleaseGroup') }
|
||||
];
|
||||
|
||||
if (allowMovieChange) {
|
||||
bulkSelectOptions.splice(1, 0, {
|
||||
@@ -371,6 +380,13 @@ class InteractiveImportModalContent extends Component {
|
||||
real={false}
|
||||
onModalClose={this.onSelectModalClose}
|
||||
/>
|
||||
|
||||
<SelectReleaseGroupModal
|
||||
isOpen={selectModalOpen === RELEASE_GROUP}
|
||||
ids={selectedIds}
|
||||
releaseGroup=""
|
||||
onModalClose={this.onSelectModalClose}
|
||||
/>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -110,7 +110,8 @@ class InteractiveImportModalContentConnector extends Component {
|
||||
const {
|
||||
movie,
|
||||
quality,
|
||||
languages
|
||||
languages,
|
||||
releaseGroup
|
||||
} = item;
|
||||
|
||||
if (!movie) {
|
||||
@@ -132,6 +133,7 @@ class InteractiveImportModalContentConnector extends Component {
|
||||
path: item.path,
|
||||
folderName: item.folderName,
|
||||
movieId: movie.id,
|
||||
releaseGroup,
|
||||
quality,
|
||||
languages,
|
||||
downloadId: this.props.downloadId
|
||||
|
||||
@@ -11,6 +11,7 @@ import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
||||
import SelectLanguageModal from 'InteractiveImport/Language/SelectLanguageModal';
|
||||
import SelectMovieModal from 'InteractiveImport/Movie/SelectMovieModal';
|
||||
import SelectQualityModal from 'InteractiveImport/Quality/SelectQualityModal';
|
||||
import SelectReleaseGroupModal from 'InteractiveImport/ReleaseGroup/SelectReleaseGroupModal';
|
||||
import MovieLanguage from 'Movie/MovieLanguage';
|
||||
import MovieQuality from 'Movie/MovieQuality';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
@@ -28,6 +29,7 @@ class InteractiveImportRow extends Component {
|
||||
|
||||
this.state = {
|
||||
isSelectMovieModalOpen: false,
|
||||
isSelectReleaseGroupModalOpen: false,
|
||||
isSelectQualityModalOpen: false,
|
||||
isSelectLanguageModalOpen: false
|
||||
};
|
||||
@@ -103,6 +105,10 @@ class InteractiveImportRow extends Component {
|
||||
this.setState({ isSelectMovieModalOpen: true });
|
||||
}
|
||||
|
||||
onSelectReleaseGroupPress = () => {
|
||||
this.setState({ isSelectReleaseGroupModalOpen: true });
|
||||
}
|
||||
|
||||
onSelectQualityPress = () => {
|
||||
this.setState({ isSelectQualityModalOpen: true });
|
||||
}
|
||||
@@ -116,6 +122,11 @@ class InteractiveImportRow extends Component {
|
||||
this.selectRowAfterChange(changed);
|
||||
}
|
||||
|
||||
onSelectReleaseGroupModalClose = (changed) => {
|
||||
this.setState({ isSelectReleaseGroupModalOpen: false });
|
||||
this.selectRowAfterChange(changed);
|
||||
}
|
||||
|
||||
onSelectQualityModalClose = (changed) => {
|
||||
this.setState({ isSelectQualityModalOpen: false });
|
||||
this.selectRowAfterChange(changed);
|
||||
@@ -137,6 +148,7 @@ class InteractiveImportRow extends Component {
|
||||
movie,
|
||||
quality,
|
||||
languages,
|
||||
releaseGroup,
|
||||
size,
|
||||
rejections,
|
||||
isReprocessing,
|
||||
@@ -147,7 +159,8 @@ class InteractiveImportRow extends Component {
|
||||
const {
|
||||
isSelectMovieModalOpen,
|
||||
isSelectQualityModalOpen,
|
||||
isSelectLanguageModalOpen
|
||||
isSelectLanguageModalOpen,
|
||||
isSelectReleaseGroupModalOpen
|
||||
} = this.state;
|
||||
|
||||
const movieTitle = movie ? movie.title + ( movie.year > 0 ? ` (${movie.year})` : '') : '';
|
||||
@@ -155,6 +168,7 @@ class InteractiveImportRow extends Component {
|
||||
const showMoviePlaceholder = isSelected && !movie;
|
||||
const showQualityPlaceholder = isSelected && !quality;
|
||||
const showLanguagePlaceholder = isSelected && !languages && !isReprocessing;
|
||||
const showReleaseGroupPlaceholder = isSelected && !releaseGroup;
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
@@ -181,6 +195,17 @@ class InteractiveImportRow extends Component {
|
||||
}
|
||||
</TableRowCellButton>
|
||||
|
||||
<TableRowCellButton
|
||||
title={translate('ClickToChangeReleaseGroup')}
|
||||
onPress={this.onSelectReleaseGroupPress}
|
||||
>
|
||||
{
|
||||
showReleaseGroupPlaceholder ?
|
||||
<InteractiveImportRowCellPlaceholder /> :
|
||||
releaseGroup
|
||||
}
|
||||
</TableRowCellButton>
|
||||
|
||||
<TableRowCellButton
|
||||
className={styles.quality}
|
||||
title={translate('ClickToChangeQuality')}
|
||||
@@ -268,6 +293,13 @@ class InteractiveImportRow extends Component {
|
||||
onModalClose={this.onSelectMovieModalClose}
|
||||
/>
|
||||
|
||||
<SelectReleaseGroupModal
|
||||
isOpen={isSelectReleaseGroupModalOpen}
|
||||
ids={[id]}
|
||||
releaseGroup={releaseGroup ?? ''}
|
||||
onModalClose={this.onSelectReleaseGroupModalClose}
|
||||
/>
|
||||
|
||||
<SelectQualityModal
|
||||
isOpen={isSelectQualityModalOpen}
|
||||
ids={[id]}
|
||||
@@ -296,6 +328,7 @@ InteractiveImportRow.propTypes = {
|
||||
movie: PropTypes.object,
|
||||
quality: PropTypes.object,
|
||||
languages: PropTypes.arrayOf(PropTypes.object),
|
||||
releaseGroup: PropTypes.string,
|
||||
size: PropTypes.number.isRequired,
|
||||
rejections: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
isReprocessing: PropTypes.bool,
|
||||
|
||||
@@ -128,7 +128,7 @@ class SelectLanguageModalContent extends Component {
|
||||
kind={kinds.SUCCESS}
|
||||
onPress={this.onLanguageSelect}
|
||||
>
|
||||
{translate('SelectLanguges')}
|
||||
{translate('SelectLanguages')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import SelectReleaseGroupModalContentConnector from './SelectReleaseGroupModalContentConnector';
|
||||
|
||||
class SelectReleaseGroupModal extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
isOpen,
|
||||
onModalClose,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<SelectReleaseGroupModalContentConnector
|
||||
{...otherProps}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectReleaseGroupModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default SelectReleaseGroupModal;
|
||||
@@ -0,0 +1,99 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import Button from 'Components/Link/Button';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
class SelectReleaseGroupModalContent extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
const {
|
||||
releaseGroup
|
||||
} = props;
|
||||
|
||||
this.state = {
|
||||
releaseGroup
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onReleaseGroupChange = ({ value }) => {
|
||||
this.setState({ releaseGroup: value });
|
||||
}
|
||||
|
||||
onReleaseGroupSelect = () => {
|
||||
this.props.onReleaseGroupSelect(this.state);
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
onModalClose
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
releaseGroup
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
{translate('ManualImportSetReleaseGroup')}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<Form>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('ReleaseGroup')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TEXT}
|
||||
name="releaseGroup"
|
||||
value={releaseGroup}
|
||||
onChange={this.onReleaseGroupChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={onModalClose}>
|
||||
{translate('Cancel')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
kind={kinds.SUCCESS}
|
||||
onPress={this.onReleaseGroupSelect}
|
||||
>
|
||||
{translate('SetReleaseGroup')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectReleaseGroupModalContent.propTypes = {
|
||||
releaseGroup: PropTypes.string.isRequired,
|
||||
onReleaseGroupSelect: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default SelectReleaseGroupModalContent;
|
||||
@@ -0,0 +1,54 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { reprocessInteractiveImportItems, updateInteractiveImportItems } from 'Store/Actions/interactiveImportActions';
|
||||
import SelectReleaseGroupModalContent from './SelectReleaseGroupModalContent';
|
||||
|
||||
const mapDispatchToProps = {
|
||||
dispatchUpdateInteractiveImportItems: updateInteractiveImportItems,
|
||||
dispatchReprocessInteractiveImportItems: reprocessInteractiveImportItems
|
||||
};
|
||||
|
||||
class SelectReleaseGroupModalContentConnector extends Component {
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onReleaseGroupSelect = ({ releaseGroup }) => {
|
||||
const {
|
||||
ids,
|
||||
dispatchUpdateInteractiveImportItems,
|
||||
dispatchReprocessInteractiveImportItems
|
||||
} = this.props;
|
||||
|
||||
dispatchUpdateInteractiveImportItems({
|
||||
ids,
|
||||
releaseGroup
|
||||
});
|
||||
|
||||
dispatchReprocessInteractiveImportItems({ ids });
|
||||
|
||||
this.props.onModalClose(true);
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<SelectReleaseGroupModalContent
|
||||
{...this.props}
|
||||
onReleaseGroupSelect={this.onReleaseGroupSelect}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectReleaseGroupModalContentConnector.propTypes = {
|
||||
ids: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
dispatchUpdateInteractiveImportItems: PropTypes.func.isRequired,
|
||||
dispatchReprocessInteractiveImportItems: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(null, mapDispatchToProps)(SelectReleaseGroupModalContentConnector);
|
||||
@@ -67,6 +67,6 @@
|
||||
width: 75px;
|
||||
}
|
||||
|
||||
.blacklist {
|
||||
.blocklist {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ class InteractiveSearchRow extends Component {
|
||||
grabError,
|
||||
historyGrabbedData,
|
||||
historyFailedData,
|
||||
blacklistData
|
||||
blocklistData
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@@ -221,12 +221,12 @@ class InteractiveSearchRow extends Component {
|
||||
}
|
||||
|
||||
{
|
||||
blacklistData?.date &&
|
||||
blocklistData?.date &&
|
||||
<Icon
|
||||
className={historyGrabbedData || historyFailedData ? styles.blacklist : ''}
|
||||
name={icons.BLACKLIST}
|
||||
className={historyGrabbedData || historyFailedData ? styles.blocklist : ''}
|
||||
name={icons.BLOCKLIST}
|
||||
kind={kinds.DANGER}
|
||||
title={`${translate('Blacklisted')}: ${formatDateTime(blacklistData.date, longDateFormat, timeFormat, { includeSeconds: true })}`}
|
||||
title={`${translate('Blocklisted')}: ${formatDateTime(blocklistData.date, longDateFormat, timeFormat, { includeSeconds: true })}`}
|
||||
/>
|
||||
}
|
||||
</TableRowCell>
|
||||
@@ -341,7 +341,7 @@ InteractiveSearchRow.propTypes = {
|
||||
onGrabPress: PropTypes.func.isRequired,
|
||||
historyFailedData: PropTypes.object,
|
||||
historyGrabbedData: PropTypes.object,
|
||||
blacklistData: PropTypes.object
|
||||
blocklistData: PropTypes.object
|
||||
};
|
||||
|
||||
InteractiveSearchRow.defaultProps = {
|
||||
|
||||
@@ -8,22 +8,22 @@ function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state, { guid }) => guid,
|
||||
(state) => state.movieHistory.items,
|
||||
(state) => state.movieBlacklist.items,
|
||||
(guid, movieHistory, movieBlacklist) => {
|
||||
(state) => state.movieBlocklist.items,
|
||||
(guid, movieHistory, movieBlocklist) => {
|
||||
|
||||
let blacklistData = {};
|
||||
let blocklistData = {};
|
||||
let historyFailedData = {};
|
||||
|
||||
const historyGrabbedData = movieHistory.find((movie) => movie.eventType === 'grabbed' && movie.data.guid === guid);
|
||||
if (historyGrabbedData) {
|
||||
historyFailedData = movieHistory.find((movie) => movie.eventType === 'downloadFailed' && movie.sourceTitle === historyGrabbedData.sourceTitle);
|
||||
blacklistData = movieBlacklist.find((item) => item.sourceTitle === historyGrabbedData.sourceTitle);
|
||||
blocklistData = movieBlocklist.find((item) => item.sourceTitle === historyGrabbedData.sourceTitle);
|
||||
}
|
||||
|
||||
return {
|
||||
historyGrabbedData,
|
||||
historyFailedData,
|
||||
blacklistData
|
||||
blocklistData
|
||||
};
|
||||
}
|
||||
);
|
||||
@@ -38,7 +38,7 @@ class InteractiveSearchRowConnector extends Component {
|
||||
const {
|
||||
historyGrabbedData,
|
||||
historyFailedData,
|
||||
blacklistData,
|
||||
blocklistData,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
@@ -46,7 +46,7 @@ class InteractiveSearchRowConnector extends Component {
|
||||
<InteractiveSearchRow
|
||||
historyGrabbedData={historyGrabbedData}
|
||||
historyFailedData={historyFailedData}
|
||||
blacklistData={blacklistData}
|
||||
blocklistData={blocklistData}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
@@ -56,7 +56,7 @@ class InteractiveSearchRowConnector extends Component {
|
||||
InteractiveSearchRowConnector.propTypes = {
|
||||
historyGrabbedData: PropTypes.object,
|
||||
historyFailedData: PropTypes.object,
|
||||
blacklistData: PropTypes.object
|
||||
blocklistData: PropTypes.object
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps)(InteractiveSearchRowConnector);
|
||||
|
||||
@@ -155,7 +155,7 @@ DeleteMovieModalContent.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
hasFile: PropTypes.bool.isRequired,
|
||||
sizeOnDisk: PropTypes.string.isRequired,
|
||||
sizeOnDisk: PropTypes.number.isRequired,
|
||||
onDeletePress: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
.header {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 350px;
|
||||
height: 375px;
|
||||
}
|
||||
|
||||
.errorMessage {
|
||||
@@ -41,8 +41,8 @@
|
||||
.poster {
|
||||
flex-shrink: 0;
|
||||
margin-right: 35px;
|
||||
width: 200px;
|
||||
height: 294px;
|
||||
width: 217px;
|
||||
height: 319px;
|
||||
}
|
||||
|
||||
.info {
|
||||
@@ -151,7 +151,8 @@
|
||||
.qualityProfileName,
|
||||
.statusName,
|
||||
.studio,
|
||||
.collection {
|
||||
.collection,
|
||||
.genres {
|
||||
font-weight: 300;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
|
||||
import TextTruncate from 'react-text-truncate';
|
||||
import HeartRating from 'Components/HeartRating';
|
||||
import Icon from 'Components/Icon';
|
||||
import ImdbRating from 'Components/ImdbRating';
|
||||
import InfoLabel from 'Components/InfoLabel';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import Marquee from 'Components/Marquee';
|
||||
@@ -16,6 +16,8 @@ import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||
import RottenTomatoRating from 'Components/RottenTomatoRating';
|
||||
import TmdbRating from 'Components/TmdbRating';
|
||||
import Popover from 'Components/Tooltip/Popover';
|
||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
|
||||
@@ -266,6 +268,7 @@ class MovieDetails extends Component {
|
||||
qualityProfileId,
|
||||
monitored,
|
||||
studio,
|
||||
genres,
|
||||
collection,
|
||||
overview,
|
||||
youTubeTrailerId,
|
||||
@@ -448,17 +451,6 @@ class MovieDetails extends Component {
|
||||
</span>
|
||||
}
|
||||
|
||||
{
|
||||
!!ratings &&
|
||||
<span className={styles.rating}>
|
||||
<HeartRating
|
||||
rating={ratings.value}
|
||||
iconSize={20}
|
||||
hideHeart={isSmallScreen}
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
|
||||
{
|
||||
<span className={styles.links}>
|
||||
<Tooltip
|
||||
@@ -500,6 +492,36 @@ class MovieDetails extends Component {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.details}>
|
||||
{
|
||||
!!ratings.tmdb &&
|
||||
<span className={styles.rating}>
|
||||
<TmdbRating
|
||||
ratings={ratings}
|
||||
iconSize={20}
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
{
|
||||
!!ratings.imdb &&
|
||||
<span className={styles.rating}>
|
||||
<ImdbRating
|
||||
ratings={ratings}
|
||||
iconSize={20}
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
{
|
||||
!!ratings.rottenTomatoes &&
|
||||
<span className={styles.rating}>
|
||||
<RottenTomatoRating
|
||||
ratings={ratings}
|
||||
iconSize={20}
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className={styles.detailsLabels}>
|
||||
<InfoLabel
|
||||
className={styles.detailsInfoLabel}
|
||||
@@ -582,6 +604,19 @@ class MovieDetails extends Component {
|
||||
</span>
|
||||
</InfoLabel>
|
||||
}
|
||||
|
||||
{
|
||||
!!genres.length && !isSmallScreen &&
|
||||
<InfoLabel
|
||||
className={styles.detailsInfoLabel}
|
||||
title={translate('Genres')}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
<span className={styles.genres}>
|
||||
{genres.join(', ')}
|
||||
</span>
|
||||
</InfoLabel>
|
||||
}
|
||||
</div>
|
||||
|
||||
<Measure onMeasure={this.onMeasure}>
|
||||
@@ -766,6 +801,7 @@ MovieDetails.propTypes = {
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
studio: PropTypes.string,
|
||||
genres: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
collection: PropTypes.object,
|
||||
youTubeTrailerId: PropTypes.string,
|
||||
isAvailable: PropTypes.bool.isRequired,
|
||||
@@ -798,6 +834,7 @@ MovieDetails.propTypes = {
|
||||
};
|
||||
|
||||
MovieDetails.defaultProps = {
|
||||
genres: [],
|
||||
tags: [],
|
||||
isSaving: false,
|
||||
sizeOnDisk: 0
|
||||
|
||||
@@ -8,7 +8,7 @@ import * as commandNames from 'Commands/commandNames';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import { clearExtraFiles, fetchExtraFiles } from 'Store/Actions/extraFileActions';
|
||||
import { toggleMovieMonitored } from 'Store/Actions/movieActions';
|
||||
import { clearMovieBlacklist, fetchMovieBlacklist } from 'Store/Actions/movieBlacklistActions';
|
||||
import { clearMovieBlocklist, fetchMovieBlocklist } from 'Store/Actions/movieBlocklistActions';
|
||||
import { clearMovieCredits, fetchMovieCredits } from 'Store/Actions/movieCreditsActions';
|
||||
import { clearMovieFiles, fetchMovieFiles } from 'Store/Actions/movieFileActions';
|
||||
import { clearMovieHistory, fetchMovieHistory } from 'Store/Actions/movieHistoryActions';
|
||||
@@ -222,11 +222,11 @@ function createMapDispatchToProps(dispatch, props) {
|
||||
onGoToMovie(titleSlug) {
|
||||
dispatch(push(`${window.Radarr.urlBase}/movie/${titleSlug}`));
|
||||
},
|
||||
dispatchFetchMovieBlacklist({ movieId }) {
|
||||
dispatch(fetchMovieBlacklist({ movieId }));
|
||||
dispatchFetchMovieBlocklist({ movieId }) {
|
||||
dispatch(fetchMovieBlocklist({ movieId }));
|
||||
},
|
||||
dispatchClearMovieBlacklist() {
|
||||
dispatch(clearMovieBlacklist());
|
||||
dispatchClearMovieBlocklist() {
|
||||
dispatch(clearMovieBlocklist());
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -280,7 +280,7 @@ class MovieDetailsConnector extends Component {
|
||||
const movieId = this.props.id;
|
||||
|
||||
this.props.dispatchFetchMovieFiles({ movieId });
|
||||
this.props.dispatchFetchMovieBlacklist({ movieId });
|
||||
this.props.dispatchFetchMovieBlocklist({ movieId });
|
||||
this.props.dispatchFetchMovieHistory({ movieId });
|
||||
this.props.dispatchFetchExtraFiles({ movieId });
|
||||
this.props.dispatchFetchMovieCredits({ movieId });
|
||||
@@ -290,7 +290,7 @@ class MovieDetailsConnector extends Component {
|
||||
|
||||
unpopulate = () => {
|
||||
this.props.dispatchCancelFetchReleases();
|
||||
this.props.dispatchClearMovieBlacklist();
|
||||
this.props.dispatchClearMovieBlocklist();
|
||||
this.props.dispatchClearMovieFiles();
|
||||
this.props.dispatchClearMovieHistory();
|
||||
this.props.dispatchClearExtraFiles();
|
||||
@@ -362,8 +362,8 @@ MovieDetailsConnector.propTypes = {
|
||||
dispatchClearQueueDetails: PropTypes.func.isRequired,
|
||||
dispatchFetchImportListSchema: PropTypes.func.isRequired,
|
||||
dispatchExecuteCommand: PropTypes.func.isRequired,
|
||||
dispatchFetchMovieBlacklist: PropTypes.func.isRequired,
|
||||
dispatchClearMovieBlacklist: PropTypes.func.isRequired,
|
||||
dispatchFetchMovieBlocklist: PropTypes.func.isRequired,
|
||||
dispatchClearMovieBlocklist: PropTypes.func.isRequired,
|
||||
onGoToMovie: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -67,8 +67,7 @@ class MovieHistoryRow extends Component {
|
||||
data,
|
||||
isMarkingAsFailed,
|
||||
shortDateFormat,
|
||||
timeFormat,
|
||||
onMarkAsFailedPress
|
||||
timeFormat
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
@@ -143,7 +142,7 @@ class MovieHistoryRow extends Component {
|
||||
isMarkingAsFailed={isMarkingAsFailed}
|
||||
shortDateFormat={shortDateFormat}
|
||||
timeFormat={timeFormat}
|
||||
onMarkAsFailedPress={onMarkAsFailedPress}
|
||||
onMarkAsFailedPress={this.onMarkAsFailedPress}
|
||||
onModalClose={this.onDetailsModalClose}
|
||||
/>
|
||||
</TableRow>
|
||||
|
||||
@@ -101,6 +101,15 @@ function MovieIndexSortMenu(props) {
|
||||
{translate('DigitalRelease')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
name="ratings"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
>
|
||||
{translate('Ratings')}
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
name="path"
|
||||
sortKey={sortKey}
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
background-color: $dangerColor;
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(90deg, color($dangerColor shade(5%)), color($dangerColor shade(5%)) 5px, color($dangerColor shade(15%)) 5px, color($dangerColor shade(15%)) 10px);
|
||||
background: repeating-linear-gradient(90deg, $dangerColor, $dangerColor 5px, $dangerColor 5px, $dimColor 10px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
background-color: $warningColor;
|
||||
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(45deg, $warningColor, $warningColor 5px, color($warningColor tint(15%)) 5px, color($warningColor tint(15%)) 10px);
|
||||
background: repeating-linear-gradient(45deg, $warningColor, $warningColor 5px, $warningColor 5px, $dimColor 10px);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ProgressBar from 'Components/ProgressBar';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import getProgressBarKind from 'Utilities/Movie/getProgressBarKind';
|
||||
import getQueueStatusText from 'Utilities/Movie/getQueueStatusText';
|
||||
import getStatusStyle from 'Utilities/Movie/getStatusStyle';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './MovieIndexProgressBar.css';
|
||||
|
||||
@@ -42,7 +42,7 @@ function MovieIndexProgressBar(props) {
|
||||
className={styles.progressBar}
|
||||
containerClassName={styles.progress}
|
||||
progress={progress}
|
||||
kind={getProgressBarKind(status, monitored, hasFile, isAvailable, queueStatusText)}
|
||||
kind={getStatusStyle(status, monitored, hasFile, isAvailable, 'kinds', queueStatusText)}
|
||||
size={detailedProgressBar ? sizes.MEDIUM : sizes.SMALL}
|
||||
showText={detailedProgressBar}
|
||||
width={posterWidth}
|
||||
|
||||
@@ -45,11 +45,16 @@
|
||||
flex: 0 0 100px;
|
||||
}
|
||||
|
||||
.movieStatus,
|
||||
.movieStatus {
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
||||
flex: 0 0 110px;
|
||||
}
|
||||
|
||||
.certification {
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
||||
flex: 0 0 100px;
|
||||
flex: 0 0 90px;
|
||||
}
|
||||
|
||||
.year {
|
||||
|
||||
@@ -52,11 +52,16 @@
|
||||
flex: 0 0 100px;
|
||||
}
|
||||
|
||||
.movieStatus,
|
||||
.movieStatus {
|
||||
composes: cell;
|
||||
|
||||
flex: 0 0 110px;
|
||||
}
|
||||
|
||||
.certification {
|
||||
composes: cell;
|
||||
|
||||
flex: 0 0 100px;
|
||||
flex: 0 0 90px;
|
||||
}
|
||||
|
||||
.year {
|
||||
|
||||
@@ -332,7 +332,7 @@ class MovieIndexRow extends Component {
|
||||
className={styles[name]}
|
||||
>
|
||||
<HeartRating
|
||||
rating={ratings.value}
|
||||
ratings={ratings}
|
||||
/>
|
||||
</VirtualTableRowCell>
|
||||
);
|
||||
|
||||
@@ -2,3 +2,45 @@
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.missingUnmonitoredBackground {
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(45deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
||||
}
|
||||
}
|
||||
|
||||
.missingMonitoredBackground {
|
||||
&:global(.colorImpaired) {
|
||||
background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
||||
}
|
||||
}
|
||||
|
||||
.queue {
|
||||
padding-right: 2px;
|
||||
border-left: 5px solid $queueColor;
|
||||
}
|
||||
|
||||
.continuing {
|
||||
padding-right: 2px;
|
||||
border-left: 5px solid $primaryColor;
|
||||
}
|
||||
|
||||
.availNotMonitored {
|
||||
padding-right: 2px;
|
||||
border-left: 5px solid $darkGray;
|
||||
}
|
||||
|
||||
.ended {
|
||||
padding-right: 2px;
|
||||
border-left: 5px solid $successColor;
|
||||
}
|
||||
|
||||
.missingMonitored {
|
||||
padding-right: 2px;
|
||||
border-left: 5px solid $dangerColor;
|
||||
}
|
||||
|
||||
.missingUnmonitored {
|
||||
padding-right: 2px;
|
||||
border-left: 5px solid $warningColor;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Label from 'Components/Label';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import MovieQuality from 'Movie/MovieQuality';
|
||||
import getQueueStatusText from 'Utilities/Movie/getQueueStatusText';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './MovieFileStatus.css';
|
||||
@@ -13,7 +11,8 @@ function MovieFileStatus(props) {
|
||||
monitored,
|
||||
movieFile,
|
||||
queueStatus,
|
||||
queueState
|
||||
queueState,
|
||||
colorImpairedMode
|
||||
} = props;
|
||||
|
||||
const hasMovieFile = !!movieFile;
|
||||
@@ -24,12 +23,8 @@ function MovieFileStatus(props) {
|
||||
|
||||
return (
|
||||
<div className={styles.center}>
|
||||
<Label
|
||||
title={queueStatusText}
|
||||
kind={kinds.QUEUE}
|
||||
>
|
||||
{queueStatusText}
|
||||
</Label>
|
||||
<span className={styles.queue} />
|
||||
{queueStatusText}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -39,51 +34,44 @@ function MovieFileStatus(props) {
|
||||
|
||||
return (
|
||||
<div className={styles.center}>
|
||||
<MovieQuality
|
||||
title={quality.quality.name}
|
||||
size={movieFile.size}
|
||||
quality={quality}
|
||||
isMonitored={monitored}
|
||||
isCutoffNotMet={movieFile.qualityCutoffNotMet}
|
||||
/>
|
||||
<span className={styles.ended} />
|
||||
{quality.quality.name}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!monitored) {
|
||||
return (
|
||||
<div className={styles.center}>
|
||||
<Label
|
||||
title={translate('NotMonitored')}
|
||||
kind={kinds.WARNING}
|
||||
>
|
||||
{translate('NotMonitored')}
|
||||
</Label>
|
||||
<div className={classNames(
|
||||
styles.center,
|
||||
styles.missingUnmonitoredBackground,
|
||||
colorImpairedMode && 'colorImpaired'
|
||||
)}
|
||||
>
|
||||
<span className={styles.missingUnmonitored} />
|
||||
{translate('NotMonitored')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (hasReleased) {
|
||||
return (
|
||||
<div className={styles.center}>
|
||||
<Label
|
||||
title={translate('MovieAvailableButMissing')}
|
||||
kind={kinds.DANGER}
|
||||
>
|
||||
{translate('Missing')}
|
||||
</Label>
|
||||
<div className={classNames(
|
||||
styles.center,
|
||||
styles.missingMonitoredBackground,
|
||||
colorImpairedMode && 'colorImpaired'
|
||||
)}
|
||||
>
|
||||
<span className={styles.missingMonitored} />
|
||||
{translate('Missing')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.center}>
|
||||
<Label
|
||||
title={translate('NotAvailable')}
|
||||
kind={kinds.INFO}
|
||||
>
|
||||
{translate('NotAvailable')}
|
||||
</Label>
|
||||
<span className={styles.continuing} />
|
||||
{translate('NotAvailable')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -93,7 +81,8 @@ MovieFileStatus.propTypes = {
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
movieFile: PropTypes.object,
|
||||
queueStatus: PropTypes.string,
|
||||
queueState: PropTypes.string
|
||||
queueState: PropTypes.string,
|
||||
colorImpairedMode: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
export default MovieFileStatus;
|
||||
|
||||
@@ -3,18 +3,21 @@ import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createMovieSelector from 'Store/Selectors/createMovieSelector';
|
||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
import MovieFileStatus from './MovieFileStatus';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createMovieSelector(),
|
||||
(movie) => {
|
||||
createUISettingsSelector(),
|
||||
(movie, uiSettings) => {
|
||||
return {
|
||||
inCinemas: movie.inCinemas,
|
||||
isAvailable: movie.isAvailable,
|
||||
monitored: movie.monitored,
|
||||
grabbed: movie.grabbed,
|
||||
movieFile: movie.movieFile
|
||||
movieFile: movie.movieFile,
|
||||
colorImpairedMode: uiSettings.enableColorImpairedMode
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@ export function getMovieStatusDetails(status) {
|
||||
let statusDetails = {
|
||||
icon: icons.ANNOUNCED,
|
||||
title: translate('Announced'),
|
||||
message: translate('AnnoucedMsg')
|
||||
message: translate('AnnouncedMsg')
|
||||
};
|
||||
|
||||
if (status === 'deleted') {
|
||||
|
||||
@@ -33,3 +33,9 @@
|
||||
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.releaseGroup {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ class MovieFileEditorRow extends Component {
|
||||
mediaInfo,
|
||||
relativePath,
|
||||
size,
|
||||
releaseGroup,
|
||||
quality,
|
||||
qualityCutoffNotMet,
|
||||
customFormats,
|
||||
@@ -155,6 +156,12 @@ class MovieFileEditorRow extends Component {
|
||||
}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell
|
||||
className={styles.releaseGroup}
|
||||
>
|
||||
{releaseGroup}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell
|
||||
className={styles.formats}
|
||||
>
|
||||
@@ -216,6 +223,7 @@ MovieFileEditorRow.propTypes = {
|
||||
size: PropTypes.number.isRequired,
|
||||
relativePath: PropTypes.string.isRequired,
|
||||
quality: PropTypes.object.isRequired,
|
||||
releaseGroup: PropTypes.string,
|
||||
customFormats: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
qualityCutoffNotMet: PropTypes.bool.isRequired,
|
||||
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
|
||||
@@ -39,6 +39,11 @@ const columns = [
|
||||
label: translate('Quality'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'releaseGroup',
|
||||
label: translate('ReleaseGroup'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'quality.customFormats',
|
||||
label: translate('Formats'),
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.rightButtons {
|
||||
justify-content: flex-end;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.addSpecification {
|
||||
composes: customFormat from '~./CustomFormat.css';
|
||||
|
||||
|
||||
@@ -198,26 +198,25 @@ class EditCustomFormatModalContent extends Component {
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
{
|
||||
id &&
|
||||
<Button
|
||||
className={styles.deleteButton}
|
||||
kind={kinds.DANGER}
|
||||
onPress={onDeleteCustomFormatPress}
|
||||
>
|
||||
{translate('Delete')}
|
||||
</Button>
|
||||
}
|
||||
<div className={styles.rightButtons}>
|
||||
{
|
||||
id &&
|
||||
<Button
|
||||
className={styles.deleteButton}
|
||||
kind={kinds.DANGER}
|
||||
onPress={onDeleteCustomFormatPress}
|
||||
>
|
||||
{translate('Delete')}
|
||||
</Button>
|
||||
}
|
||||
|
||||
{
|
||||
!id &&
|
||||
<Button
|
||||
className={styles.deleteButton}
|
||||
onPress={this.onImportPress}
|
||||
>
|
||||
{translate('Import')}
|
||||
</Button>
|
||||
}
|
||||
<Button
|
||||
className={styles.deleteButton}
|
||||
onPress={this.onImportPress}
|
||||
>
|
||||
{translate('Import')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onPress={onModalClose}
|
||||
|
||||
@@ -6,10 +6,10 @@ import { fetchCustomFormatSpecifications } from 'Store/Actions/settingsActions';
|
||||
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
||||
import ExportCustomFormatModalContent from './ExportCustomFormatModalContent';
|
||||
|
||||
const blacklistedProperties = ['id', 'implementationName', 'infoLink'];
|
||||
const omittedProperties = ['id', 'implementationName', 'infoLink'];
|
||||
|
||||
function replacer(key, value) {
|
||||
if (blacklistedProperties.includes(key)) {
|
||||
if (omittedProperties.includes(key)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ class AddSpecificationModalContent extends Component {
|
||||
</div>
|
||||
<div>
|
||||
{translate('VisitGithubCustomFormatsAphrodite')}
|
||||
<Link to="https://wiki.servarr.com/Radarr_Settings#Custom_Formats_2">{translate('Wiki')}</Link>
|
||||
<Link to="https://wiki.servarr.com/radarr/settings#custom-formats-2">{translate('Wiki')}</Link>
|
||||
</div>
|
||||
</Alert>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
@@ -13,7 +14,7 @@ import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './EditDownloadClientModalContent.css';
|
||||
|
||||
@@ -45,7 +46,10 @@ class EditDownloadClientModalContent extends Component {
|
||||
implementationName,
|
||||
name,
|
||||
enable,
|
||||
protocol,
|
||||
priority,
|
||||
removeCompletedDownloads,
|
||||
removeFailedDownloads,
|
||||
fields,
|
||||
message
|
||||
} = item;
|
||||
@@ -136,6 +140,38 @@ class EditDownloadClientModalContent extends Component {
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FieldSet
|
||||
size={sizes.SMALL}
|
||||
legend={translate('CompletedDownloadHandling')}
|
||||
>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('RemoveCompleted')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="removeCompletedDownloads"
|
||||
helpText={translate('RemoveCompletedDownloadsHelpText')}
|
||||
{...removeCompletedDownloads}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{
|
||||
protocol.value !== 'torrent' &&
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('RemoveFailed')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="removeFailedDownloads"
|
||||
helpText={translate('RemoveFailedDownloadsHelpText')}
|
||||
{...removeFailedDownloads}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
}
|
||||
</FieldSet>
|
||||
|
||||
</Form>
|
||||
}
|
||||
</ModalBody>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import { inputTypes, sizes } from 'Helpers/Props';
|
||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
||||
function DownloadClientOptions(props) {
|
||||
@@ -34,11 +35,15 @@ function DownloadClientOptions(props) {
|
||||
}
|
||||
|
||||
{
|
||||
hasSettings && !isFetching && !error &&
|
||||
hasSettings && !isFetching && !error && advancedSettings &&
|
||||
<div>
|
||||
<FieldSet legend={translate('CompletedDownloadHandling')}>
|
||||
<Form>
|
||||
<FormGroup size={sizes.MEDIUM}>
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
size={sizes.MEDIUM}
|
||||
>
|
||||
<FormLabel>{translate('Enable')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
@@ -50,22 +55,6 @@ function DownloadClientOptions(props) {
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
size={sizes.MEDIUM}
|
||||
>
|
||||
<FormLabel>{translate('Remove')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="removeCompletedDownloads"
|
||||
helpText={translate('RemoveCompletedDownloadsHelpText')}
|
||||
onChange={onInputChange}
|
||||
{...settings.removeCompletedDownloads}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
@@ -102,23 +91,10 @@ function DownloadClientOptions(props) {
|
||||
{...settings.autoRedownloadFailed}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
size={sizes.MEDIUM}
|
||||
>
|
||||
<FormLabel>{translate('Remove')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="removeFailedDownloads"
|
||||
helpText={translate('RemoveFailedDownloadsHelpText')}
|
||||
onChange={onInputChange}
|
||||
{...settings.removeFailedDownloads}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
<Alert kind={kinds.INFO}>
|
||||
{translate('RemoveDownloadsAlert')}
|
||||
</Alert>
|
||||
</FieldSet>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -51,9 +51,15 @@ class RemotePathMappings extends Component {
|
||||
{...otherProps}
|
||||
>
|
||||
<div className={styles.remotePathMappingsHeader}>
|
||||
<div className={styles.host}>Host</div>
|
||||
<div className={styles.path}>Remote Path</div>
|
||||
<div className={styles.path}>Local Path</div>
|
||||
<div className={styles.host}>
|
||||
{translate('Host')}
|
||||
</div>
|
||||
<div className={styles.path}>
|
||||
{translate('RemotePath')}
|
||||
</div>
|
||||
<div className={styles.path}>
|
||||
{translate('LocalPath')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
@@ -24,10 +24,7 @@ const requiresRestartKeys = [
|
||||
'enableSsl',
|
||||
'sslPort',
|
||||
'sslCertPath',
|
||||
'sslCertPassword',
|
||||
'authenticationMethod',
|
||||
'username',
|
||||
'password'
|
||||
'sslCertPassword'
|
||||
];
|
||||
|
||||
class GeneralSettings extends Component {
|
||||
|
||||
@@ -53,6 +53,7 @@ function HostSettings(props) {
|
||||
name="port"
|
||||
min={1}
|
||||
max={65535}
|
||||
autocomplete="off"
|
||||
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
|
||||
onChange={onInputChange}
|
||||
{...port}
|
||||
|
||||
@@ -86,7 +86,6 @@ class SecuritySettings extends Component {
|
||||
name="authenticationMethod"
|
||||
values={authenticationMethodOptions}
|
||||
helpText={translate('AuthenticationMethodHelpText')}
|
||||
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
|
||||
onChange={onInputChange}
|
||||
{...authenticationMethod}
|
||||
/>
|
||||
@@ -100,7 +99,6 @@ class SecuritySettings extends Component {
|
||||
<FormInputGroup
|
||||
type={inputTypes.TEXT}
|
||||
name="username"
|
||||
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
|
||||
onChange={onInputChange}
|
||||
{...username}
|
||||
/>
|
||||
@@ -115,7 +113,6 @@ class SecuritySettings extends Component {
|
||||
<FormInputGroup
|
||||
type={inputTypes.PASSWORD}
|
||||
name="password"
|
||||
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
|
||||
onChange={onInputChange}
|
||||
{...password}
|
||||
/>
|
||||
|
||||
@@ -55,7 +55,7 @@ function UpdateSettings(props) {
|
||||
type={inputTypes.TEXT}
|
||||
name="branch"
|
||||
helpText={usingExternalUpdateMechanism ? translate('BranchUpdateMechanism') : translate('BranchUpdate')}
|
||||
helpLink="https://wiki.servarr.com/Radarr_Settings#Updates"
|
||||
helpLink="https://wiki.servarr.com/radarr/settings#updates"
|
||||
{...branch}
|
||||
onChange={onInputChange}
|
||||
readOnly={usingExternalUpdateMechanism}
|
||||
@@ -92,7 +92,7 @@ function UpdateSettings(props) {
|
||||
name="updateMechanism"
|
||||
values={updateOptions}
|
||||
helpText={translate('UpdateMechanismHelpText')}
|
||||
helpLink="https://wiki.servarr.com/Radarr_Settings#Updates"
|
||||
helpLink="https://wiki.servarr.com/radarr/settings#updates"
|
||||
onChange={onInputChange}
|
||||
{...updateMechanism}
|
||||
/>
|
||||
|
||||
@@ -2,16 +2,16 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import EditImportExclusionModalContentConnector from './EditImportExclusionModalContentConnector';
|
||||
import EditImportListExclusionModalContentConnector from './EditImportListExclusionModalContentConnector';
|
||||
|
||||
function EditImportExclusionModal({ isOpen, onModalClose, ...otherProps }) {
|
||||
function EditImportListExclusionModal({ isOpen, onModalClose, ...otherProps }) {
|
||||
return (
|
||||
<Modal
|
||||
size={sizes.MEDIUM}
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<EditImportExclusionModalContentConnector
|
||||
<EditImportListExclusionModalContentConnector
|
||||
{...otherProps}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
@@ -19,9 +19,9 @@ function EditImportExclusionModal({ isOpen, onModalClose, ...otherProps }) {
|
||||
);
|
||||
}
|
||||
|
||||
EditImportExclusionModal.propTypes = {
|
||||
EditImportListExclusionModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default EditImportExclusionModal;
|
||||
export default EditImportListExclusionModal;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user