mirror of
https://github.com/Radarr/Radarr.git
synced 2026-04-18 21:35:51 -04:00
Compare commits
183 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| da5feda71b | |||
| 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 | |||
| f4718243ed | |||
| fcec787eb6 | |||
| 5fe8f65d64 | |||
| c2a21cd238 | |||
| a31ca4e80b | |||
| db14ac4605 | |||
| 5f229b78be | |||
| 543f2e7ddc | |||
| d6b7ab6260 | |||
| d7ab9292fb | |||
| 4300d8d8c6 | |||
| 446b2ffff9 | |||
| 695720b552 | |||
| c47934c5ca | |||
| 9938737cd7 | |||
| 58326f05e0 | |||
| 04ad5ec9c0 | |||
| 2c008384dd | |||
| a9b605c872 | |||
| cd9b469823 | |||
| df4bfa501c | |||
| 194a1e5154 | |||
| e53b2bb83c | |||
| 10772c09ef | |||
| f9ed15409a | |||
| f75ab93458 | |||
| 7755a8bd3b | |||
| 017c7998be | |||
| f40ddfef10 | |||
| 80049909eb | |||
| fc22264f89 | |||
| aba2e10b5c | |||
| 4b6874d551 | |||
| 58934a30ce | |||
| 33a960f325 | |||
| 8560ff43fe | |||
| c88a47b275 | |||
| 67fe9101d9 | |||
| af99c78352 | |||
| df3253f55c | |||
| b9abc1be11 | |||
| 5c0ee04271 | |||
| 5e2cd3798b | |||
| 83041b1d37 | |||
| 9f6c48191b | |||
| 5696fa2efe | |||
| d38311b717 | |||
| aa522066ee | |||
| d2ba70c4d7 | |||
| ca2e62492d | |||
| 02bcb4d865 | |||
| 36962f176f | |||
| 0a2afe692f | |||
| 5140ee8f2e | |||
| e47ceae0c5 | |||
| 906f8c1049 | |||
| 27e871656e |
@@ -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-->
|
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
name: Bug Report
|
||||||
|
title: "[BUG]: "
|
||||||
|
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: Anything else?
|
||||||
|
description: |
|
||||||
|
Trace Logs (https://wiki.servarr.com/radarr/troubleshooting#logging-and-log-files)
|
||||||
|
Links? References? Anything that will give us more context about the issue you are encountering!
|
||||||
|
***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.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
@@ -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. -->
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
name: Feature Request
|
||||||
|
title: "[FEAT]: "
|
||||||
|
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
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
#### Database Migration
|
#### Database Migration
|
||||||
YES | NO
|
YES - XXXX | NO
|
||||||
|
|
||||||
#### Description
|
#### Description
|
||||||
|
A few sentences describing the overall goals of the pull request's commits.
|
||||||
|
|
||||||
#### Screenshot (if UI related)
|
#### Screenshot (if UI related)
|
||||||
|
|
||||||
#### Todos
|
#### Todos
|
||||||
- [ ] Tests
|
- [ ] Tests
|
||||||
- [ ] Translation Keys
|
- [ ] Translation Keys (./src/NzbDrone.Core/Localization/Core/en.json)
|
||||||
- [ ] Wiki Updates
|
- [ ] [Wiki Updates](https://wiki.servarr.com)
|
||||||
|
|
||||||
#### Issues Fixed or Closed by this PR
|
#### Issues Fixed or Closed by this PR
|
||||||
|
|
||||||
* Fixes #XXXX
|
* Fixes #XXXX
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
todo:
|
|
||||||
keyword: "TODO"
|
|
||||||
+2
-1
@@ -4,7 +4,8 @@ daysUntilStale: 60
|
|||||||
daysUntilClose: 7
|
daysUntilClose: 7
|
||||||
# Issues with these labels will never be considered stale
|
# Issues with these labels will never be considered stale
|
||||||
exemptLabels:
|
exemptLabels:
|
||||||
- feature request
|
- feature request #legacy
|
||||||
|
- 'Type: Feature Request'
|
||||||
- 'Status: Confirmed'
|
- 'Status: Confirmed'
|
||||||
- sonarr-pull
|
- sonarr-pull
|
||||||
- lidarr-pull
|
- lidarr-pull
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
# Configuration for support-requests - https://github.com/dessant/support-requests
|
|
||||||
|
|
||||||
# Label used to mark issues as support requests
|
|
||||||
supportLabel: 'Type: Support'
|
|
||||||
# Comment to post on issues marked as support requests. Add a link
|
|
||||||
# to a support page, or set to `false` to disable
|
|
||||||
supportComment: >
|
|
||||||
We use the issue tracker exclusively for bug reports and feature requests.
|
|
||||||
However, this issue appears to be a support request. Please hop over onto our [Discord](https://radarr.video/discord) or [Subreddit](https://reddit.com/r/radarr)
|
|
||||||
# Whether to close issues marked as support requests
|
|
||||||
close: true
|
|
||||||
# Whether to lock issues marked as support requests
|
|
||||||
lock: false
|
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
name: 'Support requests'
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [labeled, unlabeled, reopened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
support:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: dessant/support-requests@v2
|
||||||
|
with:
|
||||||
|
github-token: ${{ github.token }}
|
||||||
|
support-label: 'Type: Support'
|
||||||
|
issue-comment: >
|
||||||
|
:wave: @{issue-author}, we use the issue tracker exclusively
|
||||||
|
for bug reports and feature requests. However, this issue appears
|
||||||
|
to be a support request. Please hop over onto our [Discord](https://radarr.video/discord)
|
||||||
|
or [Subreddit](https://reddit.com/r/radarr)
|
||||||
|
close-issue: true
|
||||||
|
lock-issue: false
|
||||||
+6
-42
@@ -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.
|
We're always looking for people to help make Radarr even better, there are a number of ways to contribute.
|
||||||
|
|
||||||
## Documentation ##
|
This file has been moved to the wiki for the latest details please see the [contributing wiki page](https://wiki.servarr.com/radarr/contributing).
|
||||||
Setup guides, FAQ, the more information we have on the [wiki](https://wiki.servarr.com/Radarr) the better.
|
|
||||||
|
|
||||||
## Development ##
|
## Documentation
|
||||||
|
|
||||||
### Tools required ###
|
Setup guides, [FAQ](https://wiki.servarr.com/radarr/faq), the more information we have on the [wiki](https://wiki.servarr.com/radarr) the better.
|
||||||
- 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.
|
|
||||||
|
|
||||||
### Getting started ###
|
## Development
|
||||||
|
|
||||||
1. Fork Radarr
|
See the [Wiki Page](https://wiki.servarr.com/radarr/contributing)
|
||||||
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.
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[](https://dev.azure.com/Radarr/Radarr/_build/latest?definitionId=1&branchName=develop)
|
[](https://dev.azure.com/Radarr/Radarr/_build/latest?definitionId=1&branchName=develop)
|
||||||
[](https://translate.servarr.com/engage/radarr/?utm_source=widget)
|
[](https://translate.servarr.com/engage/radarr/?utm_source=widget)
|
||||||
[](https://wiki.servarr.com/Radarr_Installation#Docker)
|
[](https://wiki.servarr.com/radarr/installation#docker)
|
||||||

|

|
||||||
[](#backers)
|
[](#backers)
|
||||||
[](#sponsors)
|
[](#sponsors)
|
||||||
@@ -34,11 +34,7 @@ Note: GitHub Issues are for Bugs and Feature Requests Only
|
|||||||
[](https://radarr.video/discord)
|
[](https://radarr.video/discord)
|
||||||
[](https://www.reddit.com/r/Radarr)
|
[](https://www.reddit.com/r/Radarr)
|
||||||
[](https://github.com/Radarr/Radarr/issues)
|
[](https://github.com/Radarr/Radarr/issues)
|
||||||
[](https://wiki.servarr.com/Radarr)
|
[](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
|
## Contributors & Developers
|
||||||
[API Documentation](https://radarr.video/docs/api/)
|
[API Documentation](https://radarr.video/docs/api/)
|
||||||
|
|||||||
+12
-62
@@ -7,13 +7,13 @@ variables:
|
|||||||
outputFolder: './_output'
|
outputFolder: './_output'
|
||||||
artifactsFolder: './_artifacts'
|
artifactsFolder: './_artifacts'
|
||||||
testsFolder: './_tests'
|
testsFolder: './_tests'
|
||||||
majorVersion: '3.1.1'
|
majorVersion: '4.0.0'
|
||||||
minorVersion: $[counter('minorVersion', 2000)]
|
minorVersion: $[counter('minorVersion', 2000)]
|
||||||
radarrVersion: '$(majorVersion).$(minorVersion)'
|
radarrVersion: '$(majorVersion).$(minorVersion)'
|
||||||
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
||||||
sentryOrg: 'servarr'
|
sentryOrg: 'servarr'
|
||||||
sentryUrl: 'https://sentry.servarr.com'
|
sentryUrl: 'https://sentry.servarr.com'
|
||||||
dotnetVersion: '5.0.202'
|
dotnetVersion: '5.0.401'
|
||||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||||
|
|
||||||
trigger:
|
trigger:
|
||||||
@@ -23,7 +23,12 @@ trigger:
|
|||||||
- master
|
- master
|
||||||
|
|
||||||
pr:
|
pr:
|
||||||
- develop
|
branches:
|
||||||
|
include:
|
||||||
|
- develop
|
||||||
|
paths:
|
||||||
|
exclude:
|
||||||
|
- src/NzbDrone.Core/Localization/Core
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- stage: Setup
|
- stage: Setup
|
||||||
@@ -110,10 +115,6 @@ stages:
|
|||||||
artifact: WindowsCoreTests
|
artifact: WindowsCoreTests
|
||||||
displayName: Publish Windows Test Package
|
displayName: Publish Windows Test Package
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
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)/net5.0/linux-x64/publish'
|
||||||
artifact: LinuxCoreTests
|
artifact: LinuxCoreTests
|
||||||
displayName: Publish Linux Test Package
|
displayName: Publish Linux Test Package
|
||||||
@@ -272,14 +273,6 @@ stages:
|
|||||||
tarCompression: 'gz'
|
tarCompression: 'gz'
|
||||||
includeRootFolder: false
|
includeRootFolder: false
|
||||||
rootFolderOrFile: $(artifactsFolder)/macos/net5.0
|
rootFolderOrFile: $(artifactsFolder)/macos/net5.0
|
||||||
- task: ArchiveFiles@2
|
|
||||||
displayName: Create Linux Mono tar
|
|
||||||
inputs:
|
|
||||||
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux.tar.gz'
|
|
||||||
archiveType: 'tar'
|
|
||||||
tarCompression: 'gz'
|
|
||||||
includeRootFolder: false
|
|
||||||
rootFolderOrFile: $(artifactsFolder)/linux-x64/net472
|
|
||||||
- task: ArchiveFiles@2
|
- task: ArchiveFiles@2
|
||||||
displayName: Create Linux Core tar
|
displayName: Create Linux Core tar
|
||||||
inputs:
|
inputs:
|
||||||
@@ -434,14 +427,6 @@ stages:
|
|||||||
- powershell: Set-Service SCardSvr -StartupType Manual
|
- powershell: Set-Service SCardSvr -StartupType Manual
|
||||||
displayName: Enable Windows Test Service
|
displayName: Enable Windows Test Service
|
||||||
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
|
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'))
|
|
||||||
- bash: find ${TESTSFOLDER} -name "Radarr.Test.Dummy" -exec chmod a+x {} \;
|
- bash: find ${TESTSFOLDER} -name "Radarr.Test.Dummy" -exec chmod a+x {} \;
|
||||||
displayName: Make Test Dummy Executable
|
displayName: Make Test Dummy Executable
|
||||||
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
|
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
|
||||||
@@ -465,18 +450,6 @@ stages:
|
|||||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
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:
|
alpine:
|
||||||
testName: 'Musl Net Core'
|
testName: 'Musl Net Core'
|
||||||
artifactName: LinuxMuslCoreTests
|
artifactName: LinuxMuslCoreTests
|
||||||
@@ -561,14 +534,6 @@ stages:
|
|||||||
vmImage: $(imageName)
|
vmImage: $(imageName)
|
||||||
|
|
||||||
steps:
|
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
|
- task: UseDotNet@2
|
||||||
displayName: 'Install .net core'
|
displayName: 'Install .net core'
|
||||||
inputs:
|
inputs:
|
||||||
@@ -651,7 +616,7 @@ stages:
|
|||||||
testResultsFormat: 'NUnit'
|
testResultsFormat: 'NUnit'
|
||||||
testResultsFiles: '**/TestResult.xml'
|
testResultsFiles: '**/TestResult.xml'
|
||||||
testRunTitle: 'FreeBSD Integration Tests'
|
testRunTitle: 'FreeBSD Integration Tests'
|
||||||
failTaskOnFailedTests: false
|
failTaskOnFailedTests: true
|
||||||
displayName: Publish Test Results
|
displayName: Publish Test Results
|
||||||
|
|
||||||
- job: Integration_Docker
|
- job: Integration_Docker
|
||||||
@@ -660,21 +625,6 @@ stages:
|
|||||||
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
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:
|
alpine:
|
||||||
testName: 'Musl Net Core'
|
testName: 'Musl Net Core'
|
||||||
artifactName: LinuxMuslCoreTests
|
artifactName: LinuxMuslCoreTests
|
||||||
@@ -911,7 +861,7 @@ stages:
|
|||||||
projectVersion: '$(radarrVersion)'
|
projectVersion: '$(radarrVersion)'
|
||||||
extraProperties: |
|
extraProperties: |
|
||||||
sonar.exclusions=**/obj/**,**/*.dll,**/NzbDrone.Core.Test/Files/**/*,./frontend/**,**/ExternalModules/**,./src/Libraries/**
|
sonar.exclusions=**/obj/**,**/*.dll,**/NzbDrone.Core.Test/Files/**/*,./frontend/**,**/ExternalModules/**,./src/Libraries/**
|
||||||
sonar.coverage.exclusions=**/Radarr.Api.V3/**/*,**/NzbDrone.Api/**/*,**/MonoTorrent/**/*,**/Marr.Data/**/*
|
sonar.coverage.exclusions=**/Radarr.Api.V3/**/*
|
||||||
sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml
|
sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml
|
||||||
sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml
|
sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml
|
||||||
- bash: |
|
- bash: |
|
||||||
@@ -946,7 +896,7 @@ stages:
|
|||||||
- job:
|
- job:
|
||||||
displayName: Discord Notification
|
displayName: Discord Notification
|
||||||
pool:
|
pool:
|
||||||
vmImage: 'windows-2019'
|
vmImage: 'ubuntu-18.04'
|
||||||
steps:
|
steps:
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
continueOnError: true
|
continueOnError: true
|
||||||
@@ -956,7 +906,7 @@ stages:
|
|||||||
artifactName: 'WindowsAutomationScreenshots'
|
artifactName: 'WindowsAutomationScreenshots'
|
||||||
targetPath: $(Build.SourcesDirectory)
|
targetPath: $(Build.SourcesDirectory)
|
||||||
- checkout: none
|
- checkout: none
|
||||||
- powershell: |
|
- pwsh: |
|
||||||
iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/Servarr/AzureDiscordNotify/master/DiscordNotify.ps1'))
|
iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/Servarr/AzureDiscordNotify/master/DiscordNotify.ps1'))
|
||||||
env:
|
env:
|
||||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ EnableBsdSupport()
|
|||||||
|
|
||||||
if grep -qv freebsd-x64 src/Directory.Build.props; then
|
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^<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
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,11 +86,11 @@ YarnInstall()
|
|||||||
ProgressEnd 'yarn install'
|
ProgressEnd 'yarn install'
|
||||||
}
|
}
|
||||||
|
|
||||||
RunGulp()
|
RunWebpack()
|
||||||
{
|
{
|
||||||
ProgressStart 'Running gulp'
|
ProgressStart 'Running webpack'
|
||||||
yarn run build --production
|
yarn run build --env production
|
||||||
ProgressEnd 'Running gulp'
|
ProgressEnd 'Running webpack'
|
||||||
}
|
}
|
||||||
|
|
||||||
PackageFiles()
|
PackageFiles()
|
||||||
@@ -148,11 +147,6 @@ PackageMacOS()
|
|||||||
|
|
||||||
PackageFiles "$folder" "$framework" "osx-x64"
|
PackageFiles "$folder" "$framework" "osx-x64"
|
||||||
|
|
||||||
if [ "$framework" = "net472" ]; then
|
|
||||||
echo "Adding Startup script"
|
|
||||||
cp macOS/Radarr $folder
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Removing Service helpers"
|
echo "Removing Service helpers"
|
||||||
rm -f $folder/ServiceUninstall.*
|
rm -f $folder/ServiceUninstall.*
|
||||||
rm -f $folder/ServiceInstall.*
|
rm -f $folder/ServiceInstall.*
|
||||||
@@ -337,7 +331,6 @@ then
|
|||||||
PackageTests "net5.0" "linux-x64"
|
PackageTests "net5.0" "linux-x64"
|
||||||
PackageTests "net5.0" "linux-musl-x64"
|
PackageTests "net5.0" "linux-musl-x64"
|
||||||
PackageTests "net5.0" "osx-x64"
|
PackageTests "net5.0" "osx-x64"
|
||||||
PackageTests "net472" "linux-x64"
|
|
||||||
if [ "$ENABLE_BSD" = "YES" ];
|
if [ "$ENABLE_BSD" = "YES" ];
|
||||||
then
|
then
|
||||||
PackageTests "net5.0" "freebsd-x64"
|
PackageTests "net5.0" "freebsd-x64"
|
||||||
@@ -350,7 +343,7 @@ fi
|
|||||||
if [ "$FRONTEND" = "YES" ];
|
if [ "$FRONTEND" = "YES" ];
|
||||||
then
|
then
|
||||||
YarnInstall
|
YarnInstall
|
||||||
RunGulp
|
RunWebpack
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$LINT" = "YES" ];
|
if [ "$LINT" = "YES" ];
|
||||||
@@ -377,7 +370,6 @@ then
|
|||||||
Package "net5.0" "linux-musl-arm64"
|
Package "net5.0" "linux-musl-arm64"
|
||||||
Package "net5.0" "linux-arm"
|
Package "net5.0" "linux-arm"
|
||||||
Package "net5.0" "osx-x64"
|
Package "net5.0" "osx-x64"
|
||||||
Package "net472" "linux-x64"
|
|
||||||
if [ "$ENABLE_BSD" = "YES" ];
|
if [ "$ENABLE_BSD" = "YES" ];
|
||||||
then
|
then
|
||||||
Package "net5.0" "freebsd-x64"
|
Package "net5.0" "freebsd-x64"
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ const dirs = fs
|
|||||||
.map((dirent) => dirent.name)
|
.map((dirent) => dirent.name)
|
||||||
.join('|');
|
.join('|');
|
||||||
|
|
||||||
|
const frontendFolder = __dirname;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
parser: 'babel-eslint',
|
parser: '@babel/eslint-parser',
|
||||||
|
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
@@ -25,6 +27,9 @@ module.exports = {
|
|||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 6,
|
ecmaVersion: 6,
|
||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
|
babelOptions: {
|
||||||
|
configFile: `${frontendFolder}/babel.config.js`
|
||||||
|
},
|
||||||
ecmaFeatures: {
|
ecmaFeatures: {
|
||||||
modules: true,
|
modules: true,
|
||||||
impliedStrict: true
|
impliedStrict: true
|
||||||
@@ -271,7 +276,7 @@ module.exports = {
|
|||||||
|
|
||||||
// ImportSort
|
// ImportSort
|
||||||
|
|
||||||
'simple-import-sort/sort': 'error',
|
'simple-import-sort/imports': 'error',
|
||||||
'import/newline-after-import': 'error',
|
'import/newline-after-import': 'error',
|
||||||
|
|
||||||
// React
|
// React
|
||||||
@@ -309,7 +314,7 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
files: ['*.js'],
|
files: ['*.js'],
|
||||||
rules: {
|
rules: {
|
||||||
'simple-import-sort/sort': [
|
'simple-import-sort/imports': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
groups: [
|
groups: [
|
||||||
|
|||||||
@@ -0,0 +1,270 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const webpack = require('webpack');
|
||||||
|
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');
|
||||||
|
const TerserPlugin = require('terser-webpack-plugin');
|
||||||
|
|
||||||
|
module.exports = (env) => {
|
||||||
|
const uiFolder = 'UI';
|
||||||
|
const frontendFolder = path.join(__dirname, '..');
|
||||||
|
const srcFolder = path.join(frontendFolder, 'src');
|
||||||
|
const isProduction = !!env.production;
|
||||||
|
const isProfiling = isProduction && !!env.profile;
|
||||||
|
const inlineWebWorkers = 'no-fallback';
|
||||||
|
|
||||||
|
const distFolder = path.resolve(frontendFolder, '..', '_output', uiFolder);
|
||||||
|
|
||||||
|
console.log('Source Folder:', srcFolder);
|
||||||
|
console.log('Output Folder:', distFolder);
|
||||||
|
console.log('isProduction:', isProduction);
|
||||||
|
console.log('isProfiling:', isProfiling);
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
mode: isProduction ? 'production' : 'development',
|
||||||
|
devtool: isProduction ? 'source-map' : 'eval-source-map',
|
||||||
|
|
||||||
|
stats: {
|
||||||
|
children: false
|
||||||
|
},
|
||||||
|
|
||||||
|
watchOptions: {
|
||||||
|
ignored: /node_modules/
|
||||||
|
},
|
||||||
|
|
||||||
|
entry: {
|
||||||
|
index: 'index.js'
|
||||||
|
},
|
||||||
|
|
||||||
|
resolve: {
|
||||||
|
modules: [
|
||||||
|
srcFolder,
|
||||||
|
path.join(srcFolder, 'Shims'),
|
||||||
|
'node_modules'
|
||||||
|
],
|
||||||
|
alias: {
|
||||||
|
jquery: 'jquery/src/jquery'
|
||||||
|
},
|
||||||
|
fallback: {
|
||||||
|
buffer: false,
|
||||||
|
http: false,
|
||||||
|
https: false,
|
||||||
|
url: false,
|
||||||
|
util: false,
|
||||||
|
net: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
output: {
|
||||||
|
path: distFolder,
|
||||||
|
publicPath: '/',
|
||||||
|
filename: '[name].js',
|
||||||
|
sourceMapFilename: '[file].map'
|
||||||
|
},
|
||||||
|
|
||||||
|
optimization: {
|
||||||
|
moduleIds: 'deterministic',
|
||||||
|
chunkIds: 'named',
|
||||||
|
splitChunks: {
|
||||||
|
chunks: 'initial',
|
||||||
|
name: 'vendors'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
performance: {
|
||||||
|
hints: false
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
__DEV__: !isProduction,
|
||||||
|
'process.env.NODE_ENV': isProduction ? JSON.stringify('production') : JSON.stringify('development')
|
||||||
|
}),
|
||||||
|
|
||||||
|
new MiniCssExtractPlugin({
|
||||||
|
filename: 'Content/styles.css'
|
||||||
|
}),
|
||||||
|
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
template: 'frontend/src/index.ejs',
|
||||||
|
filename: 'index.html',
|
||||||
|
publicPath: '/'
|
||||||
|
}),
|
||||||
|
|
||||||
|
new FileManagerPlugin({
|
||||||
|
events: {
|
||||||
|
onEnd: {
|
||||||
|
copy: [
|
||||||
|
// HTML
|
||||||
|
{
|
||||||
|
source: 'frontend/src/*.html',
|
||||||
|
destination: distFolder
|
||||||
|
},
|
||||||
|
|
||||||
|
// Fonts
|
||||||
|
{
|
||||||
|
source: 'frontend/src/Content/Fonts/*.*',
|
||||||
|
destination: path.join(distFolder, 'Content/Fonts')
|
||||||
|
},
|
||||||
|
|
||||||
|
// Icon Images
|
||||||
|
{
|
||||||
|
source: 'frontend/src/Content/Images/Icons/*.*',
|
||||||
|
destination: path.join(distFolder, 'Content/Images/Icons')
|
||||||
|
},
|
||||||
|
|
||||||
|
// Images
|
||||||
|
{
|
||||||
|
source: 'frontend/src/Content/Images/*.*',
|
||||||
|
destination: path.join(distFolder, 'Content/Images')
|
||||||
|
},
|
||||||
|
|
||||||
|
// Robots
|
||||||
|
{
|
||||||
|
source: 'frontend/src/Content/robots.txt',
|
||||||
|
destination: path.join(distFolder, 'Content/robots.txt')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
new LiveReloadPlugin()
|
||||||
|
],
|
||||||
|
|
||||||
|
resolveLoader: {
|
||||||
|
modules: [
|
||||||
|
'node_modules',
|
||||||
|
'frontend/build/webpack/'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.worker\.js$/,
|
||||||
|
use: {
|
||||||
|
loader: 'worker-loader',
|
||||||
|
options: {
|
||||||
|
filename: '[name].js',
|
||||||
|
inline: inlineWebWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.js?$/,
|
||||||
|
exclude: /(node_modules|JsLibraries)/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'babel-loader',
|
||||||
|
options: {
|
||||||
|
configFile: `${frontendFolder}/babel.config.js`,
|
||||||
|
envName: isProduction ? 'production' : 'development',
|
||||||
|
presets: [
|
||||||
|
[
|
||||||
|
'@babel/preset-env',
|
||||||
|
{
|
||||||
|
modules: false,
|
||||||
|
loose: true,
|
||||||
|
debug: false,
|
||||||
|
useBuiltIns: 'entry',
|
||||||
|
corejs: 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// CSS Modules
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
exclude: /(node_modules|globals.css)/,
|
||||||
|
use: [
|
||||||
|
{ loader: MiniCssExtractPlugin.loader },
|
||||||
|
{
|
||||||
|
loader: 'css-loader',
|
||||||
|
options: {
|
||||||
|
importLoaders: 1,
|
||||||
|
modules: {
|
||||||
|
localIdentName: '[name]/[local]/[hash:base64:5]'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'postcss-loader',
|
||||||
|
options: {
|
||||||
|
postcssOptions: {
|
||||||
|
config: 'frontend/postcss.config.js'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// Global styles
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
include: /(node_modules|globals.css)/,
|
||||||
|
use: [
|
||||||
|
'style-loader',
|
||||||
|
{
|
||||||
|
loader: 'css-loader'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// Fonts
|
||||||
|
{
|
||||||
|
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'url-loader',
|
||||||
|
options: {
|
||||||
|
limit: 10240,
|
||||||
|
mimetype: 'application/font-woff',
|
||||||
|
emitFile: false,
|
||||||
|
name: 'Content/Fonts/[name].[ext]'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
test: /\.(ttf|eot|eot?#iefix|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'file-loader',
|
||||||
|
options: {
|
||||||
|
emitFile: false,
|
||||||
|
name: 'Content/Fonts/[name].[ext]'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isProfiling) {
|
||||||
|
config.resolve.alias['react-dom$'] = 'react-dom/profiling';
|
||||||
|
config.resolve.alias['scheduler/tracing'] = 'scheduler/tracing-profiling';
|
||||||
|
|
||||||
|
config.optimization.minimizer = [
|
||||||
|
new TerserPlugin({
|
||||||
|
cache: true,
|
||||||
|
parallel: true,
|
||||||
|
sourceMap: true, // Must be set to true if using source-maps in production
|
||||||
|
terserOptions: {
|
||||||
|
mangle: false,
|
||||||
|
keep_classnames: true,
|
||||||
|
keep_fnames: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
};
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
const gulp = require('gulp');
|
|
||||||
|
|
||||||
require('./clean');
|
|
||||||
require('./copy');
|
|
||||||
require('./webpack');
|
|
||||||
|
|
||||||
gulp.task('build',
|
|
||||||
gulp.series('clean',
|
|
||||||
gulp.parallel(
|
|
||||||
'webpack',
|
|
||||||
'copyHtml',
|
|
||||||
'copyFonts',
|
|
||||||
'copyImages',
|
|
||||||
'copyRobots'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
const gulp = require('gulp');
|
|
||||||
const del = require('del');
|
|
||||||
|
|
||||||
const paths = require('./helpers/paths');
|
|
||||||
|
|
||||||
gulp.task('clean', () => {
|
|
||||||
return del([paths.dest.root]);
|
|
||||||
});
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
const path = require('path');
|
|
||||||
const gulp = require('gulp');
|
|
||||||
const print = require('gulp-print').default;
|
|
||||||
const cache = require('gulp-cached');
|
|
||||||
const livereload = require('gulp-livereload');
|
|
||||||
const paths = require('./helpers/paths.js');
|
|
||||||
|
|
||||||
gulp.task('copyHtml', () => {
|
|
||||||
return gulp.src(paths.src.html, { base: paths.src.root })
|
|
||||||
.pipe(cache('copyHtml'))
|
|
||||||
.pipe(print())
|
|
||||||
.pipe(gulp.dest(paths.dest.root))
|
|
||||||
.pipe(livereload());
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('copyFonts', () => {
|
|
||||||
return gulp.src(
|
|
||||||
path.join(paths.src.fonts, '**', '*.*'), { base: paths.src.root }
|
|
||||||
)
|
|
||||||
.pipe(cache('copyFonts'))
|
|
||||||
.pipe(print())
|
|
||||||
.pipe(gulp.dest(paths.dest.root))
|
|
||||||
.pipe(livereload());
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('copyImages', () => {
|
|
||||||
return gulp.src(
|
|
||||||
path.join(paths.src.images, '**', '*.*'), { base: paths.src.root }
|
|
||||||
)
|
|
||||||
.pipe(cache('copyImages'))
|
|
||||||
.pipe(print())
|
|
||||||
.pipe(gulp.dest(paths.dest.root))
|
|
||||||
.pipe(livereload());
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('copyRobots', () => {
|
|
||||||
return gulp.src(paths.src.robots, { base: paths.src.root })
|
|
||||||
.pipe(cache('copyRobots'))
|
|
||||||
.pipe(print())
|
|
||||||
.pipe(gulp.dest(paths.dest.root))
|
|
||||||
.pipe(livereload());
|
|
||||||
});
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
require('./build.js');
|
|
||||||
require('./clean.js');
|
|
||||||
require('./copy.js');
|
|
||||||
require('./watch.js');
|
|
||||||
require('./webpack.js');
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
const colors = require('ansi-colors');
|
|
||||||
|
|
||||||
module.exports = function errorHandler(error) {
|
|
||||||
console.log(colors.red(`Error (${error.plugin}): ${error.message}`));
|
|
||||||
this.emit('end');
|
|
||||||
};
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
const root = './frontend/src';
|
|
||||||
|
|
||||||
const paths = {
|
|
||||||
src: {
|
|
||||||
root,
|
|
||||||
html: `${root}/*.html`,
|
|
||||||
scripts: `${root}/**/*.js`,
|
|
||||||
content: `${root}/Content/`,
|
|
||||||
fonts: `${root}/Content/Fonts/`,
|
|
||||||
images: `${root}/Content/Images/`,
|
|
||||||
robots: `${root}/Content/robots.txt`,
|
|
||||||
exclude: {
|
|
||||||
libs: `!${root}/JsLibraries/**`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dest: {
|
|
||||||
root: './_output/UI/',
|
|
||||||
content: './_output/UI/Content/',
|
|
||||||
fonts: './_output/UI/Content/Fonts/',
|
|
||||||
images: './_output/UI/Content/Images/'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = paths;
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
const gulp = require('gulp');
|
|
||||||
const livereload = require('gulp-livereload');
|
|
||||||
const gulpWatch = require('gulp-watch');
|
|
||||||
const paths = require('./helpers/paths.js');
|
|
||||||
|
|
||||||
require('./copy.js');
|
|
||||||
require('./webpack.js');
|
|
||||||
|
|
||||||
function watch() {
|
|
||||||
livereload.listen({ start: true });
|
|
||||||
|
|
||||||
gulp.task('webpackWatch')();
|
|
||||||
gulpWatch(paths.src.html, gulp.series('copyHtml'));
|
|
||||||
gulpWatch(`${paths.src.fonts}**/*.*`, gulp.series('copyFonts'));
|
|
||||||
gulpWatch(`${paths.src.images}**/*.*`, gulp.series('copyImages'));
|
|
||||||
gulpWatch(paths.src.robots, gulp.series('copyRobots'));
|
|
||||||
}
|
|
||||||
|
|
||||||
gulp.task('watch', gulp.series('build', watch));
|
|
||||||
@@ -1,275 +0,0 @@
|
|||||||
const gulp = require('gulp');
|
|
||||||
const webpackStream = require('webpack-stream');
|
|
||||||
const livereload = require('gulp-livereload');
|
|
||||||
const path = require('path');
|
|
||||||
const webpack = require('webpack');
|
|
||||||
const errorHandler = require('./helpers/errorHandler');
|
|
||||||
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
|
||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
||||||
const HtmlWebpackPluginHtmlTags = require('html-webpack-plugin/lib/html-tags');
|
|
||||||
const TerserPlugin = require('terser-webpack-plugin');
|
|
||||||
|
|
||||||
const uiFolder = 'UI';
|
|
||||||
const frontendFolder = path.join(__dirname, '..');
|
|
||||||
const srcFolder = path.join(frontendFolder, 'src');
|
|
||||||
const isProduction = process.argv.indexOf('--production') > -1;
|
|
||||||
const isProfiling = isProduction && process.argv.indexOf('--profile') > -1;
|
|
||||||
const inlineWebWorkers = 'no-fallback';
|
|
||||||
|
|
||||||
const distFolder = path.resolve(frontendFolder, '..', '_output', uiFolder);
|
|
||||||
|
|
||||||
console.log('Source Folder:', srcFolder);
|
|
||||||
console.log('Output Folder:', distFolder);
|
|
||||||
console.log('isProduction:', isProduction);
|
|
||||||
console.log('isProfiling:', isProfiling);
|
|
||||||
|
|
||||||
const cssVarsFiles = [
|
|
||||||
'../src/Styles/Variables/colors',
|
|
||||||
'../src/Styles/Variables/dimensions',
|
|
||||||
'../src/Styles/Variables/fonts',
|
|
||||||
'../src/Styles/Variables/animations',
|
|
||||||
'../src/Styles/Variables/zIndexes'
|
|
||||||
].map(require.resolve);
|
|
||||||
|
|
||||||
// Override the way HtmlWebpackPlugin injects the scripts
|
|
||||||
// TODO: Find a better way to get these paths without
|
|
||||||
HtmlWebpackPlugin.prototype.injectAssetsIntoHtml = function(html, assets, assetTags) {
|
|
||||||
const head = assetTags.headTags.map((v) => {
|
|
||||||
const href = v.attributes.href
|
|
||||||
.replace('\\', '/')
|
|
||||||
.replace('%5C', '/');
|
|
||||||
|
|
||||||
v.attributes = { rel: 'stylesheet', type: 'text/css', href: `/${href}` };
|
|
||||||
return HtmlWebpackPluginHtmlTags.htmlTagObjectToString(v, this.options.xhtml);
|
|
||||||
});
|
|
||||||
const body = assetTags.bodyTags.map((v) => {
|
|
||||||
v.attributes = { src: `/${v.attributes.src}` };
|
|
||||||
return HtmlWebpackPluginHtmlTags.htmlTagObjectToString(v, this.options.xhtml);
|
|
||||||
});
|
|
||||||
|
|
||||||
return html
|
|
||||||
.replace('<!-- webpack bundles head -->', head.join('\r\n '))
|
|
||||||
.replace('<!-- webpack bundles body -->', body.join('\r\n '));
|
|
||||||
};
|
|
||||||
|
|
||||||
const plugins = [
|
|
||||||
new webpack.IgnorePlugin({
|
|
||||||
resourceRegExp: /(fetch-cookie|node-fetch|tough-cookie)/
|
|
||||||
}),
|
|
||||||
|
|
||||||
new OptimizeCssAssetsPlugin({}),
|
|
||||||
|
|
||||||
new webpack.DefinePlugin({
|
|
||||||
__DEV__: !isProduction,
|
|
||||||
'process.env.NODE_ENV': isProduction ? JSON.stringify('production') : JSON.stringify('development')
|
|
||||||
}),
|
|
||||||
|
|
||||||
new MiniCssExtractPlugin({
|
|
||||||
filename: path.join('Content', 'styles.css')
|
|
||||||
}),
|
|
||||||
|
|
||||||
new HtmlWebpackPlugin({
|
|
||||||
template: 'frontend/src/index.html',
|
|
||||||
filename: 'index.html'
|
|
||||||
})
|
|
||||||
];
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
mode: isProduction ? 'production' : 'development',
|
|
||||||
devtool: '#source-map',
|
|
||||||
|
|
||||||
stats: {
|
|
||||||
children: false
|
|
||||||
},
|
|
||||||
|
|
||||||
watchOptions: {
|
|
||||||
ignored: /node_modules/
|
|
||||||
},
|
|
||||||
|
|
||||||
entry: {
|
|
||||||
index: 'index.js'
|
|
||||||
},
|
|
||||||
|
|
||||||
resolve: {
|
|
||||||
modules: [
|
|
||||||
srcFolder,
|
|
||||||
path.join(srcFolder, 'Shims'),
|
|
||||||
'node_modules'
|
|
||||||
],
|
|
||||||
alias: {
|
|
||||||
jquery: 'jquery/src/jquery'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
output: {
|
|
||||||
path: distFolder,
|
|
||||||
filename: '[name].js',
|
|
||||||
sourceMapFilename: '[file].map'
|
|
||||||
},
|
|
||||||
|
|
||||||
optimization: {
|
|
||||||
chunkIds: 'named',
|
|
||||||
splitChunks: {
|
|
||||||
chunks: 'initial'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
performance: {
|
|
||||||
hints: false
|
|
||||||
},
|
|
||||||
|
|
||||||
plugins,
|
|
||||||
|
|
||||||
resolveLoader: {
|
|
||||||
modules: [
|
|
||||||
'node_modules',
|
|
||||||
'frontend/gulp/webpack/'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.worker\.js$/,
|
|
||||||
use: {
|
|
||||||
loader: 'worker-loader',
|
|
||||||
options: {
|
|
||||||
filename: '[name].js',
|
|
||||||
inline: inlineWebWorkers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.js?$/,
|
|
||||||
exclude: /(node_modules|JsLibraries)/,
|
|
||||||
use: [
|
|
||||||
{
|
|
||||||
loader: 'babel-loader',
|
|
||||||
options: {
|
|
||||||
configFile: `${frontendFolder}/babel.config.js`,
|
|
||||||
envName: isProduction ? 'production' : 'development',
|
|
||||||
presets: [
|
|
||||||
[
|
|
||||||
'@babel/preset-env',
|
|
||||||
{
|
|
||||||
modules: false,
|
|
||||||
loose: true,
|
|
||||||
debug: false,
|
|
||||||
useBuiltIns: 'entry',
|
|
||||||
corejs: 3
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
// CSS Modules
|
|
||||||
{
|
|
||||||
test: /\.css$/,
|
|
||||||
exclude: /(node_modules|globals.css)/,
|
|
||||||
use: [
|
|
||||||
{ loader: MiniCssExtractPlugin.loader },
|
|
||||||
{
|
|
||||||
loader: 'css-loader',
|
|
||||||
options: {
|
|
||||||
importLoaders: 1,
|
|
||||||
modules: {
|
|
||||||
localIdentName: '[name]/[local]/[hash:base64:5]'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
loader: 'postcss-loader',
|
|
||||||
options: {
|
|
||||||
ident: 'postcss',
|
|
||||||
config: {
|
|
||||||
ctx: {
|
|
||||||
cssVarsFiles
|
|
||||||
},
|
|
||||||
path: 'frontend/postcss.config.js'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
// Global styles
|
|
||||||
{
|
|
||||||
test: /\.css$/,
|
|
||||||
include: /(node_modules|globals.css)/,
|
|
||||||
use: [
|
|
||||||
'style-loader',
|
|
||||||
{
|
|
||||||
loader: 'css-loader'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
// Fonts
|
|
||||||
{
|
|
||||||
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
|
||||||
use: [
|
|
||||||
{
|
|
||||||
loader: 'url-loader',
|
|
||||||
options: {
|
|
||||||
limit: 10240,
|
|
||||||
mimetype: 'application/font-woff',
|
|
||||||
emitFile: false,
|
|
||||||
name: 'Content/Fonts/[name].[ext]'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
test: /\.(ttf|eot|eot?#iefix|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
|
||||||
use: [
|
|
||||||
{
|
|
||||||
loader: 'file-loader',
|
|
||||||
options: {
|
|
||||||
emitFile: false,
|
|
||||||
name: 'Content/Fonts/[name].[ext]'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isProfiling) {
|
|
||||||
config.resolve.alias['react-dom$'] = 'react-dom/profiling';
|
|
||||||
config.resolve.alias['scheduler/tracing'] = 'scheduler/tracing-profiling';
|
|
||||||
|
|
||||||
config.optimization.minimizer = [
|
|
||||||
new TerserPlugin({
|
|
||||||
cache: true,
|
|
||||||
parallel: true,
|
|
||||||
sourceMap: true, // Must be set to true if using source-maps in production
|
|
||||||
terserOptions: {
|
|
||||||
mangle: false,
|
|
||||||
keep_classnames: true,
|
|
||||||
keep_fnames: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
gulp.task('webpack', () => {
|
|
||||||
return webpackStream(config)
|
|
||||||
.pipe(gulp.dest('_output/UI'));
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('webpackWatch', () => {
|
|
||||||
config.watch = true;
|
|
||||||
|
|
||||||
return webpackStream(config, webpack)
|
|
||||||
.on('error', errorHandler)
|
|
||||||
.pipe(gulp.dest('_output/UI'))
|
|
||||||
.on('error', errorHandler)
|
|
||||||
.pipe(livereload())
|
|
||||||
.on('error', errorHandler);
|
|
||||||
});
|
|
||||||
+29
-20
@@ -1,23 +1,32 @@
|
|||||||
const reload = require('require-nocache')(module);
|
const reload = require('require-nocache')(module);
|
||||||
|
|
||||||
module.exports = (ctx, configPath, options) => {
|
const cssVarsFiles = [
|
||||||
const config = {
|
'./src/Styles/Variables/colors',
|
||||||
plugins: {
|
'./src/Styles/Variables/dimensions',
|
||||||
'postcss-mixins': {
|
'./src/Styles/Variables/fonts',
|
||||||
mixinsDir: [
|
'./src/Styles/Variables/animations',
|
||||||
'frontend/src/Styles/Mixins'
|
'./src/Styles/Variables/zIndexes'
|
||||||
]
|
].map(require.resolve);
|
||||||
},
|
|
||||||
'postcss-simple-vars': {
|
|
||||||
variables: () =>
|
|
||||||
ctx.options.cssVarsFiles.reduce((acc, vars) => {
|
|
||||||
return Object.assign(acc, reload(vars));
|
|
||||||
}, {})
|
|
||||||
},
|
|
||||||
'postcss-color-function': {},
|
|
||||||
'postcss-nested': {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return config;
|
const mixinsFiles = [
|
||||||
};
|
'frontend/src/Styles/Mixins/cover.css',
|
||||||
|
'frontend/src/Styles/Mixins/linkOverlay.css',
|
||||||
|
'frontend/src/Styles/Mixins/scroller.css',
|
||||||
|
'frontend/src/Styles/Mixins/truncate.css'
|
||||||
|
];
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
plugins: [
|
||||||
|
['postcss-mixins', {
|
||||||
|
mixinsFiles
|
||||||
|
}],
|
||||||
|
['postcss-simple-vars', {
|
||||||
|
variables: () =>
|
||||||
|
cssVarsFiles.reduce((acc, vars) => {
|
||||||
|
return Object.assign(acc, reload(vars));
|
||||||
|
}, {})
|
||||||
|
}],
|
||||||
|
'postcss-color-function',
|
||||||
|
'postcss-nested'
|
||||||
|
]
|
||||||
|
};
|
||||||
@@ -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)
|
|
||||||
);
|
|
||||||
+14
-14
@@ -19,9 +19,9 @@ import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
|||||||
import removeOldSelectedState from 'Utilities/Table/removeOldSelectedState';
|
import removeOldSelectedState from 'Utilities/Table/removeOldSelectedState';
|
||||||
import selectAll from 'Utilities/Table/selectAll';
|
import selectAll from 'Utilities/Table/selectAll';
|
||||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||||
import BlacklistRowConnector from './BlacklistRowConnector';
|
import BlocklistRowConnector from './BlocklistRowConnector';
|
||||||
|
|
||||||
class Blacklist extends Component {
|
class Blocklist extends Component {
|
||||||
|
|
||||||
//
|
//
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
@@ -101,8 +101,8 @@ class Blacklist extends Component {
|
|||||||
columns,
|
columns,
|
||||||
totalRecords,
|
totalRecords,
|
||||||
isRemoving,
|
isRemoving,
|
||||||
isClearingBlacklistExecuting,
|
isClearingBlocklistExecuting,
|
||||||
onClearBlacklistPress,
|
onClearBlocklistPress,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ class Blacklist extends Component {
|
|||||||
const selectedIds = this.getSelectedIds();
|
const selectedIds = this.getSelectedIds();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContent title={translate('Blacklist')}>
|
<PageContent title={translate('Blocklist')}>
|
||||||
<PageToolbar>
|
<PageToolbar>
|
||||||
<PageToolbarSection>
|
<PageToolbarSection>
|
||||||
<PageToolbarButton
|
<PageToolbarButton
|
||||||
@@ -130,8 +130,8 @@ class Blacklist extends Component {
|
|||||||
<PageToolbarButton
|
<PageToolbarButton
|
||||||
label={translate('Clear')}
|
label={translate('Clear')}
|
||||||
iconName={icons.CLEAR}
|
iconName={icons.CLEAR}
|
||||||
isSpinning={isClearingBlacklistExecuting}
|
isSpinning={isClearingBlocklistExecuting}
|
||||||
onPress={onClearBlacklistPress}
|
onPress={onClearBlocklistPress}
|
||||||
/>
|
/>
|
||||||
</PageToolbarSection>
|
</PageToolbarSection>
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ class Blacklist extends Component {
|
|||||||
{
|
{
|
||||||
!isFetching && !!error &&
|
!isFetching && !!error &&
|
||||||
<div>
|
<div>
|
||||||
{translate('UnableToLoadBlacklist')}
|
{translate('UnableToLoadBlocklist')}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +183,7 @@ class Blacklist extends Component {
|
|||||||
{
|
{
|
||||||
items.map((item) => {
|
items.map((item) => {
|
||||||
return (
|
return (
|
||||||
<BlacklistRowConnector
|
<BlocklistRowConnector
|
||||||
key={item.id}
|
key={item.id}
|
||||||
isSelected={selectedState[item.id] || false}
|
isSelected={selectedState[item.id] || false}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
@@ -209,7 +209,7 @@ class Blacklist extends Component {
|
|||||||
isOpen={isConfirmRemoveModalOpen}
|
isOpen={isConfirmRemoveModalOpen}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={translate('RemoveSelected')}
|
title={translate('RemoveSelected')}
|
||||||
message={translate('AreYouSureYouWantToRemoveTheSelectedItemsFromBlacklist')}
|
message={translate('AreYouSureYouWantToRemoveTheSelectedItemsFromBlocklist')}
|
||||||
confirmLabel={translate('RemoveSelected')}
|
confirmLabel={translate('RemoveSelected')}
|
||||||
onConfirm={this.onRemoveSelectedConfirmed}
|
onConfirm={this.onRemoveSelectedConfirmed}
|
||||||
onCancel={this.onConfirmRemoveModalClose}
|
onCancel={this.onConfirmRemoveModalClose}
|
||||||
@@ -219,7 +219,7 @@ class Blacklist extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Blacklist.propTypes = {
|
Blocklist.propTypes = {
|
||||||
isFetching: PropTypes.bool.isRequired,
|
isFetching: PropTypes.bool.isRequired,
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
error: PropTypes.object,
|
error: PropTypes.object,
|
||||||
@@ -227,9 +227,9 @@ Blacklist.propTypes = {
|
|||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
totalRecords: PropTypes.number,
|
totalRecords: PropTypes.number,
|
||||||
isRemoving: PropTypes.bool.isRequired,
|
isRemoving: PropTypes.bool.isRequired,
|
||||||
isClearingBlacklistExecuting: PropTypes.bool.isRequired,
|
isClearingBlocklistExecuting: PropTypes.bool.isRequired,
|
||||||
onRemoveSelected: PropTypes.func.isRequired,
|
onRemoveSelected: PropTypes.func.isRequired,
|
||||||
onClearBlacklistPress: PropTypes.func.isRequired
|
onClearBlocklistPress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Blacklist;
|
export default Blocklist;
|
||||||
@@ -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)
|
||||||
|
);
|
||||||
+3
-3
@@ -10,7 +10,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
|
|||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
|
|
||||||
class BlacklistDetailsModal extends Component {
|
class BlocklistDetailsModal extends Component {
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
@@ -78,7 +78,7 @@ class BlacklistDetailsModal extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BlacklistDetailsModal.propTypes = {
|
BlocklistDetailsModal.propTypes = {
|
||||||
isOpen: PropTypes.bool.isRequired,
|
isOpen: PropTypes.bool.isRequired,
|
||||||
sourceTitle: PropTypes.string.isRequired,
|
sourceTitle: PropTypes.string.isRequired,
|
||||||
protocol: PropTypes.string.isRequired,
|
protocol: PropTypes.string.isRequired,
|
||||||
@@ -87,4 +87,4 @@ BlacklistDetailsModal.propTypes = {
|
|||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BlacklistDetailsModal;
|
export default BlocklistDetailsModal;
|
||||||
+7
-7
@@ -11,10 +11,10 @@ import MovieLanguage from 'Movie/MovieLanguage';
|
|||||||
import MovieQuality from 'Movie/MovieQuality';
|
import MovieQuality from 'Movie/MovieQuality';
|
||||||
import MovieTitleLink from 'Movie/MovieTitleLink';
|
import MovieTitleLink from 'Movie/MovieTitleLink';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import BlacklistDetailsModal from './BlacklistDetailsModal';
|
import BlocklistDetailsModal from './BlocklistDetailsModal';
|
||||||
import styles from './BlacklistRow.css';
|
import styles from './BlocklistRow.css';
|
||||||
|
|
||||||
class BlacklistRow extends Component {
|
class BlocklistRow extends Component {
|
||||||
|
|
||||||
//
|
//
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
@@ -166,7 +166,7 @@ class BlacklistRow extends Component {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
title={translate('RemoveFromBlacklist')}
|
title={translate('RemoveFromBlocklist')}
|
||||||
name={icons.REMOVE}
|
name={icons.REMOVE}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
onPress={onRemovePress}
|
onPress={onRemovePress}
|
||||||
@@ -179,7 +179,7 @@ class BlacklistRow extends Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
<BlacklistDetailsModal
|
<BlocklistDetailsModal
|
||||||
isOpen={this.state.isDetailsModalOpen}
|
isOpen={this.state.isDetailsModalOpen}
|
||||||
sourceTitle={sourceTitle}
|
sourceTitle={sourceTitle}
|
||||||
protocol={protocol}
|
protocol={protocol}
|
||||||
@@ -193,7 +193,7 @@ class BlacklistRow extends Component {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BlacklistRow.propTypes = {
|
BlocklistRow.propTypes = {
|
||||||
id: PropTypes.number.isRequired,
|
id: PropTypes.number.isRequired,
|
||||||
movie: PropTypes.object.isRequired,
|
movie: PropTypes.object.isRequired,
|
||||||
sourceTitle: PropTypes.string.isRequired,
|
sourceTitle: PropTypes.string.isRequired,
|
||||||
@@ -210,4 +210,4 @@ BlacklistRow.propTypes = {
|
|||||||
onRemovePress: PropTypes.func.isRequired
|
onRemovePress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BlacklistRow;
|
export default BlocklistRow;
|
||||||
+4
-4
@@ -1,8 +1,8 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { removeBlacklistItem } from 'Store/Actions/blacklistActions';
|
import { removeBlocklistItem } from 'Store/Actions/blocklistActions';
|
||||||
import createMovieSelector from 'Store/Selectors/createMovieSelector';
|
import createMovieSelector from 'Store/Selectors/createMovieSelector';
|
||||||
import BlacklistRow from './BlacklistRow';
|
import BlocklistRow from './BlocklistRow';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
@@ -18,9 +18,9 @@ function createMapStateToProps() {
|
|||||||
function createMapDispatchToProps(dispatch, props) {
|
function createMapDispatchToProps(dispatch, props) {
|
||||||
return {
|
return {
|
||||||
onRemovePress() {
|
onRemovePress() {
|
||||||
dispatch(removeBlacklistItem({ id: props.id }));
|
dispatch(removeBlocklistItem({ id: props.id }));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(createMapStateToProps, createMapDispatchToProps)(BlacklistRow);
|
export default connect(createMapStateToProps, createMapDispatchToProps)(BlocklistRow);
|
||||||
@@ -42,14 +42,14 @@ class QueueRow extends Component {
|
|||||||
this.setState({ isRemoveQueueItemModalOpen: true });
|
this.setState({ isRemoveQueueItemModalOpen: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
onRemoveQueueItemModalConfirmed = (blacklist) => {
|
onRemoveQueueItemModalConfirmed = (blocklist) => {
|
||||||
const {
|
const {
|
||||||
onRemoveQueueItemPress,
|
onRemoveQueueItemPress,
|
||||||
onQueueRowModalOpenOrClose
|
onQueueRowModalOpenOrClose
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
onQueueRowModalOpenOrClose(false);
|
onQueueRowModalOpenOrClose(false);
|
||||||
onRemoveQueueItemPress(blacklist);
|
onRemoveQueueItemPress(blocklist);
|
||||||
|
|
||||||
this.setState({ isRemoveQueueItemModalOpen: false });
|
this.setState({ isRemoveQueueItemModalOpen: false });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class RemoveQueueItemModal extends Component {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
remove: true,
|
remove: true,
|
||||||
blacklist: false
|
blocklist: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ class RemoveQueueItemModal extends Component {
|
|||||||
resetState = function() {
|
resetState = function() {
|
||||||
this.setState({
|
this.setState({
|
||||||
remove: true,
|
remove: true,
|
||||||
blacklist: false
|
blocklist: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,8 +43,8 @@ class RemoveQueueItemModal extends Component {
|
|||||||
this.setState({ remove: value });
|
this.setState({ remove: value });
|
||||||
}
|
}
|
||||||
|
|
||||||
onBlacklistChange = ({ value }) => {
|
onBlocklistChange = ({ value }) => {
|
||||||
this.setState({ blacklist: value });
|
this.setState({ blocklist: value });
|
||||||
}
|
}
|
||||||
|
|
||||||
onRemoveConfirmed = () => {
|
onRemoveConfirmed = () => {
|
||||||
@@ -69,7 +69,7 @@ class RemoveQueueItemModal extends Component {
|
|||||||
canIgnore
|
canIgnore
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const { remove, blacklist } = this.state;
|
const { remove, blocklist } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@@ -103,13 +103,13 @@ class RemoveQueueItemModal extends Component {
|
|||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>{translate('BlacklistRelease')}</FormLabel>
|
<FormLabel>{translate('BlocklistRelease')}</FormLabel>
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
name="blacklist"
|
name="blocklist"
|
||||||
value={blacklist}
|
value={blocklist}
|
||||||
helpText={translate('BlacklistHelpText')}
|
helpText={translate('BlocklistHelpText')}
|
||||||
onChange={this.onBlacklistChange}
|
onChange={this.onBlocklistChange}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class RemoveQueueItemsModal extends Component {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
remove: true,
|
remove: true,
|
||||||
blacklist: false
|
blocklist: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ class RemoveQueueItemsModal extends Component {
|
|||||||
resetState = function() {
|
resetState = function() {
|
||||||
this.setState({
|
this.setState({
|
||||||
remove: true,
|
remove: true,
|
||||||
blacklist: false
|
blocklist: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,8 +44,8 @@ class RemoveQueueItemsModal extends Component {
|
|||||||
this.setState({ remove: value });
|
this.setState({ remove: value });
|
||||||
}
|
}
|
||||||
|
|
||||||
onBlacklistChange = ({ value }) => {
|
onBlocklistChange = ({ value }) => {
|
||||||
this.setState({ blacklist: value });
|
this.setState({ blocklist: value });
|
||||||
}
|
}
|
||||||
|
|
||||||
onRemoveConfirmed = () => {
|
onRemoveConfirmed = () => {
|
||||||
@@ -70,7 +70,7 @@ class RemoveQueueItemsModal extends Component {
|
|||||||
canIgnore
|
canIgnore
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const { remove, blacklist } = this.state;
|
const { remove, blocklist } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@@ -105,15 +105,15 @@ class RemoveQueueItemsModal extends Component {
|
|||||||
|
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
Blacklist Release{selectedCount > 1 ? 's' : ''}
|
Blocklist Release{selectedCount > 1 ? 's' : ''}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
name="blacklist"
|
name="blocklist"
|
||||||
value={blacklist}
|
value={blocklist}
|
||||||
helpText={translate('BlacklistHelpText')}
|
helpText={translate('BlocklistHelpText')}
|
||||||
onChange={this.onBlacklistChange}
|
onChange={this.onBlocklistChange}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ class AddNewMovie extends Component {
|
|||||||
className={styles.searchInput}
|
className={styles.searchInput}
|
||||||
name="movieLookup"
|
name="movieLookup"
|
||||||
value={term}
|
value={term}
|
||||||
placeholder="eg. The Dark Knight, tmdb:155, imdb:tt0468569"
|
placeholder="e.g. The Dark Knight, tmdb:155, imdb:tt0468569"
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
onChange={this.onSearchInputChange}
|
onChange={this.onSearchInputChange}
|
||||||
/>
|
/>
|
||||||
@@ -161,7 +161,7 @@ class AddNewMovie extends Component {
|
|||||||
{translate('YouCanAlsoSearch')}
|
{translate('YouCanAlsoSearch')}
|
||||||
</div>
|
</div>
|
||||||
<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-cant-i-add-a-new-movie-to-radarr">
|
||||||
{translate('CantFindMovie')}
|
{translate('CantFindMovie')}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -92,6 +92,19 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.certification {
|
||||||
|
margin-left: 2px;
|
||||||
|
padding: 0 5px;
|
||||||
|
border: 1px solid;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.runtime {
|
||||||
|
margin-left: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.statusContainer {
|
.statusContainer {
|
||||||
margin-right: 22px;
|
margin-right: 22px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -103,10 +116,3 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.certification {
|
|
||||||
margin-right: 5px;
|
|
||||||
padding: 0 5px;
|
|
||||||
border: 1px solid;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ class AddNewMovieSearchResult extends Component {
|
|||||||
|
|
||||||
{
|
{
|
||||||
!!runtime &&
|
!!runtime &&
|
||||||
<span>
|
<span className={styles.runtime}>
|
||||||
{formatRuntime(runtime, movieRuntimeFormat)}
|
{formatRuntime(runtime, movieRuntimeFormat)}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,3 +30,9 @@
|
|||||||
.importButtonIcon {
|
.importButtonIcon {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.addErrorAlert {
|
||||||
|
composes: alert from '~Components/Alert.css';
|
||||||
|
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import Alert from 'Components/Alert';
|
||||||
import FieldSet from 'Components/FieldSet';
|
import FieldSet from 'Components/FieldSet';
|
||||||
import FileBrowserModal from 'Components/FileBrowser/FileBrowserModal';
|
import FileBrowserModal from 'Components/FileBrowser/FileBrowserModal';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
@@ -72,23 +73,29 @@ class ImportMovieSelectFolder extends Component {
|
|||||||
isWindows,
|
isWindows,
|
||||||
isFetching,
|
isFetching,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
|
isSaving,
|
||||||
error,
|
error,
|
||||||
|
saveError,
|
||||||
items
|
items
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const hasRootFolders = items.length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContent title={translate('ImportMovies')}>
|
<PageContent title={translate('ImportMovies')}>
|
||||||
<PageContentBody>
|
<PageContentBody>
|
||||||
{
|
{
|
||||||
isFetching && !isPopulated &&
|
isFetching && !isPopulated ?
|
||||||
<LoadingIndicator />
|
<LoadingIndicator /> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!isFetching && !!error &&
|
!isFetching && error ?
|
||||||
<div>
|
<div>
|
||||||
{translate('UnableToLoadRootFolders')}
|
{translate('UnableToLoadRootFolders')}
|
||||||
</div>
|
</div> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -108,7 +115,7 @@ class ImportMovieSelectFolder extends Component {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
items.length > 0 ?
|
hasRootFolders ?
|
||||||
<div className={styles.recentFolders}>
|
<div className={styles.recentFolders}>
|
||||||
<FieldSet legend={translate('RecentFolders')}>
|
<FieldSet legend={translate('RecentFolders')}>
|
||||||
<Table
|
<Table
|
||||||
@@ -131,35 +138,51 @@ class ImportMovieSelectFolder extends Component {
|
|||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</FieldSet>
|
</FieldSet>
|
||||||
|
|
||||||
<Button
|
|
||||||
kind={kinds.PRIMARY}
|
|
||||||
size={sizes.LARGE}
|
|
||||||
onPress={this.onAddNewRootFolderPress}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
className={styles.importButtonIcon}
|
|
||||||
name={icons.DRIVE}
|
|
||||||
/>
|
|
||||||
{translate('ChooseAnotherFolder')}
|
|
||||||
</Button>
|
|
||||||
</div> :
|
</div> :
|
||||||
|
null
|
||||||
<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>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
!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
|
<FileBrowserModal
|
||||||
isOpen={this.state.isAddNewRootFolderModalOpen}
|
isOpen={this.state.isAddNewRootFolderModalOpen}
|
||||||
name="rootFolderPath"
|
name="rootFolderPath"
|
||||||
@@ -179,7 +202,9 @@ ImportMovieSelectFolder.propTypes = {
|
|||||||
isWindows: PropTypes.bool.isRequired,
|
isWindows: PropTypes.bool.isRequired,
|
||||||
isFetching: PropTypes.bool.isRequired,
|
isFetching: PropTypes.bool.isRequired,
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
|
isSaving: PropTypes.bool.isRequired,
|
||||||
error: PropTypes.object,
|
error: PropTypes.object,
|
||||||
|
saveError: PropTypes.object,
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
onNewRootFolderSelect: PropTypes.func.isRequired,
|
onNewRootFolderSelect: PropTypes.func.isRequired,
|
||||||
onDeleteRootFolderPress: PropTypes.func.isRequired
|
onDeleteRootFolderPress: PropTypes.func.isRequired
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Redirect, Route } from 'react-router-dom';
|
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 HistoryConnector from 'Activity/History/HistoryConnector';
|
||||||
import QueueConnector from 'Activity/Queue/QueueConnector';
|
import QueueConnector from 'Activity/Queue/QueueConnector';
|
||||||
import AddNewMovieConnector from 'AddMovie/AddNewMovie/AddNewMovieConnector';
|
import AddNewMovieConnector from 'AddMovie/AddNewMovie/AddNewMovieConnector';
|
||||||
@@ -111,8 +111,8 @@ function AppRoutes(props) {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="/activity/blacklist"
|
path="/activity/blocklist"
|
||||||
component={BlacklistConnector}
|
component={BlocklistConnector}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/*
|
{/*
|
||||||
|
|||||||
@@ -54,20 +54,24 @@
|
|||||||
composes: downloaded from '~Calendar/Events/CalendarEvent.css';
|
composes: downloaded from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.downloading {
|
.queue {
|
||||||
composes: downloading from '~Calendar/Events/CalendarEvent.css';
|
composes: queue from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.unmonitored {
|
.unmonitored {
|
||||||
composes: unmonitored from '~Calendar/Events/CalendarEvent.css';
|
composes: unmonitored from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.missing {
|
.missingUnmonitored {
|
||||||
composes: missing from '~Calendar/Events/CalendarEvent.css';
|
composes: missingUnmonitored from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.unreleased {
|
.missingMonitored {
|
||||||
composes: unreleased from '~Calendar/Events/CalendarEvent.css';
|
composes: missingMonitored from '~Calendar/Events/CalendarEvent.css';
|
||||||
|
}
|
||||||
|
|
||||||
|
.continuing {
|
||||||
|
composes: continuing from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: $breakpointSmall) {
|
@media only screen and (max-width: $breakpointSmall) {
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import moment from 'moment';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import CalendarEventQueueDetails from 'Calendar/Events/CalendarEventQueueDetails';
|
import CalendarEventQueueDetails from 'Calendar/Events/CalendarEventQueueDetails';
|
||||||
import getStatusStyle from 'Calendar/getStatusStyle';
|
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
import { icons, kinds } from 'Helpers/Props';
|
import { icons, kinds } from 'Helpers/Props';
|
||||||
|
import getStatusStyle from 'Utilities/Movie/getStatusStyle';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import styles from './AgendaEvent.css';
|
import styles from './AgendaEvent.css';
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ class AgendaEvent extends Component {
|
|||||||
startTime = moment(startTime);
|
startTime = moment(startTime);
|
||||||
const downloading = !!(queueItem || grabbed);
|
const downloading = !!(queueItem || grabbed);
|
||||||
const isMonitored = monitored;
|
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 joinedGenres = genres.slice(0, 2).join(', ');
|
||||||
const link = `/movie/${titleSlug}`;
|
const link = `/movie/${titleSlug}`;
|
||||||
|
|
||||||
|
|||||||
@@ -60,39 +60,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.downloading {
|
.queue {
|
||||||
border-left-color: $purple !important;
|
border-left-color: $purple !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.unmonitored {
|
.unmonitored {
|
||||||
border-left-color: $gray !important;
|
border-left-color: $gray !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.missingUnmonitored {
|
||||||
|
border-left-color: $warningColor !important;
|
||||||
|
|
||||||
&:global(.colorImpaired) {
|
&:global(.colorImpaired) {
|
||||||
background: repeating-linear-gradient(45deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
background: repeating-linear-gradient(45deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.onAir {
|
.missingMonitored {
|
||||||
border-left-color: $warningColor !important;
|
|
||||||
|
|
||||||
&:global(.colorImpaired) {
|
|
||||||
background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.missing {
|
|
||||||
border-left-color: $dangerColor !important;
|
border-left-color: $dangerColor !important;
|
||||||
|
|
||||||
&:global(.colorImpaired) {
|
&:global(.colorImpaired) {
|
||||||
border-left-color: color($dangerColor saturation(+15%)) !important;
|
|
||||||
background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.unreleased {
|
.continuing {
|
||||||
border-left-color: $primaryColor !important;
|
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 moment from 'moment';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import getStatusStyle from 'Calendar/getStatusStyle';
|
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
import { icons, kinds } from 'Helpers/Props';
|
import { icons, kinds } from 'Helpers/Props';
|
||||||
|
import getStatusStyle from 'Utilities/Movie/getStatusStyle';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import CalendarEventQueueDetails from './CalendarEventQueueDetails';
|
import CalendarEventQueueDetails from './CalendarEventQueueDetails';
|
||||||
import styles from './CalendarEvent.css';
|
import styles from './CalendarEvent.css';
|
||||||
@@ -38,7 +38,7 @@ class CalendarEvent extends Component {
|
|||||||
|
|
||||||
const isDownloading = !!(queueItem || grabbed);
|
const isDownloading = !!(queueItem || grabbed);
|
||||||
const isMonitored = monitored;
|
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 joinedGenres = genres.slice(0, 2).join(', ');
|
||||||
const link = `/movie/${titleSlug}`;
|
const link = `/movie/${titleSlug}`;
|
||||||
const eventType = [];
|
const eventType = [];
|
||||||
|
|||||||
@@ -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 APPLICATION_UPDATE = 'ApplicationUpdate';
|
||||||
export const BACKUP = 'Backup';
|
export const BACKUP = 'Backup';
|
||||||
export const REFRESH_MONITORED_DOWNLOADS = 'RefreshMonitoredDownloads';
|
export const REFRESH_MONITORED_DOWNLOADS = 'RefreshMonitoredDownloads';
|
||||||
export const CLEAR_BLACKLIST = 'ClearBlacklist';
|
export const CLEAR_BLOCKLIST = 'ClearBlocklist';
|
||||||
export const CLEAR_LOGS = 'ClearLog';
|
export const CLEAR_LOGS = 'ClearLog';
|
||||||
export const CUTOFF_UNMET_MOVIES_SEARCH = 'CutoffUnmetMoviesSearch';
|
export const CUTOFF_UNMET_MOVIES_SEARCH = 'CutoffUnmetMoviesSearch';
|
||||||
export const DELETE_LOG_FILES = 'DeleteLogFiles';
|
export const DELETE_LOG_FILES = 'DeleteLogFiles';
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ class FileBrowserModalContent extends Component {
|
|||||||
className={styles.mappedDrivesWarning}
|
className={styles.mappedDrivesWarning}
|
||||||
kind={kinds.WARNING}
|
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')}
|
{translate('MappedDrivesRunningAsService')}
|
||||||
</Link> .
|
</Link> .
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ function createTagListSelector() {
|
|||||||
(selectedFilterBuilderProp.type === filterBuilderTypes.NUMBER ||
|
(selectedFilterBuilderProp.type === filterBuilderTypes.NUMBER ||
|
||||||
selectedFilterBuilderProp.type === filterBuilderTypes.STRING) &&
|
selectedFilterBuilderProp.type === filterBuilderTypes.STRING) &&
|
||||||
filterType !== filterTypes.EQUAL &&
|
filterType !== filterTypes.EQUAL &&
|
||||||
filterType !== filterBuilderTypes.NOT_EQUAL ||
|
filterType !== filterTypes.NOT_EQUAL ||
|
||||||
!selectedFilterBuilderProp.optionsSelector
|
!selectedFilterBuilderProp.optionsSelector
|
||||||
) {
|
) {
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
@@ -479,6 +479,7 @@ class EnhancedSelectInput extends Component {
|
|||||||
<OptionComponent
|
<OptionComponent
|
||||||
key={v.key}
|
key={v.key}
|
||||||
id={v.key}
|
id={v.key}
|
||||||
|
dividerAfter={v.dividerAfter}
|
||||||
depth={depth}
|
depth={depth}
|
||||||
isSelected={isSelectedItem(index, this.props)}
|
isSelected={isSelectedItem(index, this.props)}
|
||||||
isDisabled={parentSelected}
|
isDisabled={parentSelected}
|
||||||
@@ -539,6 +540,7 @@ class EnhancedSelectInput extends Component {
|
|||||||
<OptionComponent
|
<OptionComponent
|
||||||
key={v.key}
|
key={v.key}
|
||||||
id={v.key}
|
id={v.key}
|
||||||
|
dividerAfter={v.dividerAfter}
|
||||||
depth={depth}
|
depth={depth}
|
||||||
isSelected={isSelectedItem(index, this.props)}
|
isSelected={isSelectedItem(index, this.props)}
|
||||||
isMultiSelect={isMultiSelect}
|
isMultiSelect={isMultiSelect}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import EnhancedSelectInputConnector from './EnhancedSelectInputConnector';
|
|||||||
import FormInputHelpText from './FormInputHelpText';
|
import FormInputHelpText from './FormInputHelpText';
|
||||||
import IndexerFlagsSelectInputConnector from './IndexerFlagsSelectInputConnector';
|
import IndexerFlagsSelectInputConnector from './IndexerFlagsSelectInputConnector';
|
||||||
import KeyValueListInput from './KeyValueListInput';
|
import KeyValueListInput from './KeyValueListInput';
|
||||||
|
import LanguageSelectInputConnector from './LanguageSelectInputConnector';
|
||||||
import MovieMonitoredSelectInput from './MovieMonitoredSelectInput';
|
import MovieMonitoredSelectInput from './MovieMonitoredSelectInput';
|
||||||
import NumberInput from './NumberInput';
|
import NumberInput from './NumberInput';
|
||||||
import OAuthInputConnector from './OAuthInputConnector';
|
import OAuthInputConnector from './OAuthInputConnector';
|
||||||
@@ -72,6 +73,9 @@ function getComponent(type) {
|
|||||||
case inputTypes.INDEXER_FLAGS_SELECT:
|
case inputTypes.INDEXER_FLAGS_SELECT:
|
||||||
return IndexerFlagsSelectInputConnector;
|
return IndexerFlagsSelectInputConnector;
|
||||||
|
|
||||||
|
case inputTypes.LANGUAGE_SELECT:
|
||||||
|
return LanguageSelectInputConnector;
|
||||||
|
|
||||||
case inputTypes.SELECT:
|
case inputTypes.SELECT:
|
||||||
return EnhancedSelectInput;
|
return EnhancedSelectInput;
|
||||||
|
|
||||||
|
|||||||
@@ -21,3 +21,8 @@
|
|||||||
color: $darkGray;
|
color: $darkGray;
|
||||||
font-size: $smallFontSize;
|
font-size: $smallFontSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid $lightGray;
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,37 +12,46 @@ function HintedSelectInputOption(props) {
|
|||||||
depth,
|
depth,
|
||||||
isSelected,
|
isSelected,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
|
dividerAfter,
|
||||||
isMultiSelect,
|
isMultiSelect,
|
||||||
isMobile,
|
isMobile,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EnhancedSelectInputOption
|
<div>
|
||||||
id={id}
|
<EnhancedSelectInputOption
|
||||||
depth={depth}
|
id={id}
|
||||||
isSelected={isSelected}
|
depth={depth}
|
||||||
isDisabled={isDisabled}
|
isSelected={isSelected}
|
||||||
isHidden={isDisabled}
|
isDisabled={isDisabled}
|
||||||
isMultiSelect={isMultiSelect}
|
isHidden={isDisabled}
|
||||||
isMobile={isMobile}
|
isMultiSelect={isMultiSelect}
|
||||||
{...otherProps}
|
isMobile={isMobile}
|
||||||
>
|
{...otherProps}
|
||||||
<div className={classNames(
|
|
||||||
styles.optionText,
|
|
||||||
isMobile && styles.isMobile
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<div>{value}</div>
|
<div className={classNames(
|
||||||
|
styles.optionText,
|
||||||
|
isMobile && styles.isMobile
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div>{value}</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
hint != null &&
|
hint != null &&
|
||||||
<div className={styles.hintText}>
|
<div className={styles.hintText}>
|
||||||
{hint}
|
{hint}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</EnhancedSelectInputOption>
|
</EnhancedSelectInputOption>
|
||||||
|
|
||||||
|
{
|
||||||
|
dividerAfter ?
|
||||||
|
<div className={styles.divider} /> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,15 +59,18 @@ HintedSelectInputOption.propTypes = {
|
|||||||
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||||
value: PropTypes.string.isRequired,
|
value: PropTypes.string.isRequired,
|
||||||
hint: PropTypes.node,
|
hint: PropTypes.node,
|
||||||
|
name: PropTypes.string,
|
||||||
depth: PropTypes.number,
|
depth: PropTypes.number,
|
||||||
isSelected: PropTypes.bool.isRequired,
|
isSelected: PropTypes.bool.isRequired,
|
||||||
isDisabled: PropTypes.bool.isRequired,
|
isDisabled: PropTypes.bool.isRequired,
|
||||||
|
dividerAfter: PropTypes.bool.isRequired,
|
||||||
isMultiSelect: PropTypes.bool.isRequired,
|
isMultiSelect: PropTypes.bool.isRequired,
|
||||||
isMobile: PropTypes.bool.isRequired
|
isMobile: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
HintedSelectInputOption.defaultProps = {
|
HintedSelectInputOption.defaultProps = {
|
||||||
isDisabled: false,
|
isDisabled: false,
|
||||||
|
dividerAfter: false,
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
isMultiSelect: false
|
isMultiSelect: false
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
(state, { values }) => values,
|
||||||
|
( languages ) => {
|
||||||
|
|
||||||
|
const minId = languages.reduce((min, v) => (v.key < 1 ? v.key : min), languages[0].key);
|
||||||
|
|
||||||
|
const values = languages.map(({ key, value }) => {
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
dividerAfter: minId < 1 ? key === minId : false
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
values
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class LanguageSelectInputConnector extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EnhancedSelectInput
|
||||||
|
{...this.props}
|
||||||
|
onChange={this.props.onChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LanguageSelectInputConnector.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
value: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.number), PropTypes.number]).isRequired,
|
||||||
|
values: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
onChange: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps)(LanguageSelectInputConnector);
|
||||||
@@ -49,6 +49,7 @@ function getSelectValues(selectOptions) {
|
|||||||
result.push({
|
result.push({
|
||||||
key: option.value,
|
key: option.value,
|
||||||
value: option.name,
|
value: option.name,
|
||||||
|
dividerAfter: option.dividerAfter,
|
||||||
hint: option.hint
|
hint: option.hint
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -38,11 +38,12 @@ class Link extends Component {
|
|||||||
const linkProps = { target };
|
const linkProps = { target };
|
||||||
let el = component;
|
let el = component;
|
||||||
|
|
||||||
if (to) {
|
if (to && typeof to === 'string') {
|
||||||
if ((/\w+?:\/\//).test(to)) {
|
if ((/\w+?:\/\//).test(to)) {
|
||||||
el = 'a';
|
el = 'a';
|
||||||
linkProps.href = to;
|
linkProps.href = to;
|
||||||
linkProps.target = target || '_blank';
|
linkProps.target = target || '_blank';
|
||||||
|
linkProps.rel = 'noreferrer';
|
||||||
} else if (noRouter) {
|
} else if (noRouter) {
|
||||||
el = 'a';
|
el = 'a';
|
||||||
linkProps.href = to;
|
linkProps.href = to;
|
||||||
@@ -52,6 +53,18 @@ class Link extends Component {
|
|||||||
linkProps.to = `${window.Radarr.urlBase}/${to.replace(/^\//, '')}`;
|
linkProps.to = `${window.Radarr.urlBase}/${to.replace(/^\//, '')}`;
|
||||||
linkProps.target = target;
|
linkProps.target = target;
|
||||||
}
|
}
|
||||||
|
} else if (to && typeof to === 'object') {
|
||||||
|
el = RouterLink;
|
||||||
|
linkProps.target = target;
|
||||||
|
if (to.pathname.startsWith(`${window.Radarr.urlBase}/`)) {
|
||||||
|
linkProps.to = to;
|
||||||
|
} else {
|
||||||
|
const pathname = `${window.Radarr.urlBase}/${to.pathname.replace(/^\//, '')}`;
|
||||||
|
linkProps.to = {
|
||||||
|
...to,
|
||||||
|
pathname
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (el === 'button' || el === 'input') {
|
if (el === 'button' || el === 'input') {
|
||||||
@@ -82,7 +95,7 @@ class Link extends Component {
|
|||||||
Link.propTypes = {
|
Link.propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
component: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
component: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
||||||
to: PropTypes.string,
|
to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||||
target: PropTypes.string,
|
target: PropTypes.string,
|
||||||
isDisabled: PropTypes.bool,
|
isDisabled: PropTypes.bool,
|
||||||
noRouter: PropTypes.bool,
|
noRouter: PropTypes.bool,
|
||||||
|
|||||||
@@ -53,7 +53,13 @@ class PageHeader extends Component {
|
|||||||
return (
|
return (
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<div className={styles.logoContainer}>
|
<div className={styles.logoContainer}>
|
||||||
<Link to={'/'}>
|
<Link
|
||||||
|
className={styles.logoLink}
|
||||||
|
to={{
|
||||||
|
pathname: '/',
|
||||||
|
state: { restoreScrollPosition: true }
|
||||||
|
}}
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
className={isSmallScreen ? styles.logo : styles.logoFull}
|
className={isSmallScreen ? styles.logo : styles.logoFull}
|
||||||
src={isSmallScreen ? `${window.Radarr.urlBase}/Content/Images/logo.png` : `${window.Radarr.urlBase}/Content/Images/logo-full.png`}
|
src={isSmallScreen ? `${window.Radarr.urlBase}/Content/Images/logo.png` : `${window.Radarr.urlBase}/Content/Images/logo-full.png`}
|
||||||
|
|||||||
@@ -61,8 +61,8 @@ const links = [
|
|||||||
to: '/activity/history'
|
to: '/activity/history'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: translate('Blacklist'),
|
title: translate('Blocklist'),
|
||||||
to: '/activity/blacklist'
|
to: '/activity/blocklist'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
background-color: $dangerColor;
|
background-color: $dangerColor;
|
||||||
|
|
||||||
&:global(.colorImpaired) {
|
&:global(.colorImpaired) {
|
||||||
background: repeating-linear-gradient(90deg, color($dangerColor shade(5%)), color($dangerColor shade(5%)) 5px, color($dangerColor shade(15%)) 5px, color($dangerColor shade(15%)) 10px);
|
background: repeating-linear-gradient(90deg, $dangerColor, $dangerColor 5px, $dangerColor 5px, $dimColor 10px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
background-color: $warningColor;
|
background-color: $warningColor;
|
||||||
|
|
||||||
&:global(.colorImpaired) {
|
&:global(.colorImpaired) {
|
||||||
background: repeating-linear-gradient(45deg, $warningColor, $warningColor 5px, color($warningColor tint(15%)) 5px, color($warningColor tint(15%)) 10px);
|
background: repeating-linear-gradient(45deg, $warningColor, $warningColor 5px, $warningColor 5px, $dimColor 10px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { DndProvider } from 'react-dnd';
|
import { DndProvider } from 'react-dnd-multi-backend';
|
||||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
import HTML5toTouch from 'react-dnd-multi-backend/dist/esm/HTML5toTouch';
|
||||||
import Form from 'Components/Form/Form';
|
import Form from 'Components/Form/Form';
|
||||||
import FormGroup from 'Components/Form/FormGroup';
|
import FormGroup from 'Components/Form/FormGroup';
|
||||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||||
@@ -128,7 +128,7 @@ class TableOptionsModal extends Component {
|
|||||||
const isDraggingDown = isDragging && dropIndex > dragIndex;
|
const isDraggingDown = isDragging && dropIndex > dragIndex;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DndProvider backend={HTML5Backend}>
|
<DndProvider options={HTML5toTouch}>
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onModalClose={onModalClose}
|
onModalClose={onModalClose}
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ class VirtualTable extends Component {
|
|||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
width: 0
|
width: 0,
|
||||||
|
scrollRestored: false
|
||||||
};
|
};
|
||||||
|
|
||||||
this._grid = null;
|
this._grid = null;
|
||||||
@@ -48,11 +49,13 @@ class VirtualTable extends Component {
|
|||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
scrollIndex
|
scrollIndex,
|
||||||
|
scrollTop
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
width
|
width,
|
||||||
|
scrollRestored
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
if (this._grid &&
|
if (this._grid &&
|
||||||
@@ -68,6 +71,11 @@ class VirtualTable extends Component {
|
|||||||
columnIndex: 0
|
columnIndex: 0
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._grid && scrollTop !== undefined && scrollTop !== 0 && !scrollRestored) {
|
||||||
|
this.setState({ scrollRestored: true });
|
||||||
|
this._grid.scrollToPosition({ scrollTop });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -96,6 +104,7 @@ class VirtualTable extends Component {
|
|||||||
items,
|
items,
|
||||||
scroller,
|
scroller,
|
||||||
focusScroller,
|
focusScroller,
|
||||||
|
scrollTop: ignored,
|
||||||
header,
|
header,
|
||||||
headerHeight,
|
headerHeight,
|
||||||
rowRenderer,
|
rowRenderer,
|
||||||
@@ -180,6 +189,7 @@ VirtualTable.propTypes = {
|
|||||||
className: PropTypes.string.isRequired,
|
className: PropTypes.string.isRequired,
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
scrollIndex: PropTypes.number,
|
scrollIndex: PropTypes.number,
|
||||||
|
scrollTop: PropTypes.number,
|
||||||
scroller: PropTypes.instanceOf(Element).isRequired,
|
scroller: PropTypes.instanceOf(Element).isRequired,
|
||||||
focusScroller: PropTypes.bool.isRequired,
|
focusScroller: PropTypes.bool.isRequired,
|
||||||
header: PropTypes.node.isRequired,
|
header: PropTypes.node.isRequired,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ function withScrollPosition(WrappedComponent, scrollPositionKey) {
|
|||||||
history
|
history
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const scrollTop = history.action === 'POP' ?
|
const scrollTop = history.action === 'POP' || (history.location.state && history.location.state.restoreScrollPosition) ?
|
||||||
scrollPositions[scrollPositionKey] :
|
scrollPositions[scrollPositionKey] :
|
||||||
0;
|
0;
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
@@ -184,6 +184,7 @@ export const PAGE_LAST = fasFastForward;
|
|||||||
export const PARENT = fasLevelUpAlt;
|
export const PARENT = fasLevelUpAlt;
|
||||||
export const PAUSED = fasPause;
|
export const PAUSED = fasPause;
|
||||||
export const PENDING = farClock;
|
export const PENDING = farClock;
|
||||||
|
export const PLAY = fasPlay;
|
||||||
export const PROFILE = fasUser;
|
export const PROFILE = fasUser;
|
||||||
export const POSTER = fasTh;
|
export const POSTER = fasTh;
|
||||||
export const QUEUED = fasCloud;
|
export const QUEUED = fasCloud;
|
||||||
@@ -224,4 +225,4 @@ export const UNSAVED_SETTING = farDotCircle;
|
|||||||
export const VIEW = fasEye;
|
export const VIEW = fasEye;
|
||||||
export const WARNING = fasExclamationTriangle;
|
export const WARNING = fasExclamationTriangle;
|
||||||
export const WIKI = fasBookReader;
|
export const WIKI = fasBookReader;
|
||||||
export const BLACKLIST = fasBan;
|
export const BLOCKLIST = fasBan;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ export const AVAILABILITY_SELECT = 'availabilitySelect';
|
|||||||
export const CAPTCHA = 'captcha';
|
export const CAPTCHA = 'captcha';
|
||||||
export const CHECK = 'check';
|
export const CHECK = 'check';
|
||||||
export const DEVICE = 'device';
|
export const DEVICE = 'device';
|
||||||
|
export const KEY_VALUE_LIST = 'keyValueList';
|
||||||
export const MOVIE_MONITORED_SELECT = 'movieMonitoredSelect';
|
export const MOVIE_MONITORED_SELECT = 'movieMonitoredSelect';
|
||||||
export const NUMBER = 'number';
|
export const NUMBER = 'number';
|
||||||
export const OAUTH = 'oauth';
|
export const OAUTH = 'oauth';
|
||||||
@@ -11,6 +12,7 @@ export const PATH = 'path';
|
|||||||
export const QUALITY_PROFILE_SELECT = 'qualityProfileSelect';
|
export const QUALITY_PROFILE_SELECT = 'qualityProfileSelect';
|
||||||
export const ROOT_FOLDER_SELECT = 'rootFolderSelect';
|
export const ROOT_FOLDER_SELECT = 'rootFolderSelect';
|
||||||
export const INDEXER_FLAGS_SELECT = 'indexerFlagsSelect';
|
export const INDEXER_FLAGS_SELECT = 'indexerFlagsSelect';
|
||||||
|
export const LANGUAGE_SELECT = 'languageSelect';
|
||||||
export const SELECT = 'select';
|
export const SELECT = 'select';
|
||||||
export const DYNAMIC_SELECT = 'dynamicSelect';
|
export const DYNAMIC_SELECT = 'dynamicSelect';
|
||||||
export const TAG = 'tag';
|
export const TAG = 'tag';
|
||||||
@@ -26,6 +28,7 @@ export const all = [
|
|||||||
CAPTCHA,
|
CAPTCHA,
|
||||||
CHECK,
|
CHECK,
|
||||||
DEVICE,
|
DEVICE,
|
||||||
|
KEY_VALUE_LIST,
|
||||||
MOVIE_MONITORED_SELECT,
|
MOVIE_MONITORED_SELECT,
|
||||||
NUMBER,
|
NUMBER,
|
||||||
OAUTH,
|
OAUTH,
|
||||||
@@ -34,6 +37,7 @@ export const all = [
|
|||||||
QUALITY_PROFILE_SELECT,
|
QUALITY_PROFILE_SELECT,
|
||||||
ROOT_FOLDER_SELECT,
|
ROOT_FOLDER_SELECT,
|
||||||
INDEXER_FLAGS_SELECT,
|
INDEXER_FLAGS_SELECT,
|
||||||
|
LANGUAGE_SELECT,
|
||||||
SELECT,
|
SELECT,
|
||||||
DYNAMIC_SELECT,
|
DYNAMIC_SELECT,
|
||||||
TAG,
|
TAG,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export const DANGER = 'danger';
|
export const DANGER = 'danger';
|
||||||
export const DEFAULT = 'default';
|
export const DEFAULT = 'default';
|
||||||
|
export const DELETE = 'delete';
|
||||||
export const DISABLED = 'disabled';
|
export const DISABLED = 'disabled';
|
||||||
export const INFO = 'info';
|
export const INFO = 'info';
|
||||||
export const INVERSE = 'inverse';
|
export const INVERSE = 'inverse';
|
||||||
@@ -13,6 +14,7 @@ export const QUEUE = 'queue';
|
|||||||
export const all = [
|
export const all = [
|
||||||
DANGER,
|
DANGER,
|
||||||
DEFAULT,
|
DEFAULT,
|
||||||
|
DELETE,
|
||||||
DISABLED,
|
DISABLED,
|
||||||
INFO,
|
INFO,
|
||||||
INVERSE,
|
INVERSE,
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ const columns = [
|
|||||||
name: icons.DANGER,
|
name: icons.DANGER,
|
||||||
kind: kinds.DANGER
|
kind: kinds.DANGER
|
||||||
}),
|
}),
|
||||||
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -67,6 +67,6 @@
|
|||||||
width: 75px;
|
width: 75px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blacklist {
|
.blocklist {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ class InteractiveSearchRow extends Component {
|
|||||||
grabError,
|
grabError,
|
||||||
historyGrabbedData,
|
historyGrabbedData,
|
||||||
historyFailedData,
|
historyFailedData,
|
||||||
blacklistData
|
blocklistData
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -180,7 +180,7 @@ class InteractiveSearchRow extends Component {
|
|||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
}
|
}
|
||||||
position={tooltipPositions.LEFT}
|
position={tooltipPositions.BOTTOM}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
@@ -221,12 +221,12 @@ class InteractiveSearchRow extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
blacklistData?.date &&
|
blocklistData?.date &&
|
||||||
<Icon
|
<Icon
|
||||||
className={historyGrabbedData || historyFailedData ? styles.blacklist : ''}
|
className={historyGrabbedData || historyFailedData ? styles.blocklist : ''}
|
||||||
name={icons.BLACKLIST}
|
name={icons.BLOCKLIST}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
title={`${translate('Blacklisted')}: ${formatDateTime(blacklistData.date, longDateFormat, timeFormat, { includeSeconds: true })}`}
|
title={`${translate('Blocklisted')}: ${formatDateTime(blocklistData.date, longDateFormat, timeFormat, { includeSeconds: true })}`}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
@@ -292,7 +292,7 @@ class InteractiveSearchRow extends Component {
|
|||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
}
|
}
|
||||||
position={tooltipPositions.LEFT}
|
position={tooltipPositions.BOTTOM}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
@@ -341,7 +341,7 @@ InteractiveSearchRow.propTypes = {
|
|||||||
onGrabPress: PropTypes.func.isRequired,
|
onGrabPress: PropTypes.func.isRequired,
|
||||||
historyFailedData: PropTypes.object,
|
historyFailedData: PropTypes.object,
|
||||||
historyGrabbedData: PropTypes.object,
|
historyGrabbedData: PropTypes.object,
|
||||||
blacklistData: PropTypes.object
|
blocklistData: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
InteractiveSearchRow.defaultProps = {
|
InteractiveSearchRow.defaultProps = {
|
||||||
|
|||||||
@@ -8,22 +8,22 @@ function createMapStateToProps() {
|
|||||||
return createSelector(
|
return createSelector(
|
||||||
(state, { guid }) => guid,
|
(state, { guid }) => guid,
|
||||||
(state) => state.movieHistory.items,
|
(state) => state.movieHistory.items,
|
||||||
(state) => state.movieBlacklist.items,
|
(state) => state.movieBlocklist.items,
|
||||||
(guid, movieHistory, movieBlacklist) => {
|
(guid, movieHistory, movieBlocklist) => {
|
||||||
|
|
||||||
let blacklistData = {};
|
let blocklistData = {};
|
||||||
let historyFailedData = {};
|
let historyFailedData = {};
|
||||||
|
|
||||||
const historyGrabbedData = movieHistory.find((movie) => movie.eventType === 'grabbed' && movie.data.guid === guid);
|
const historyGrabbedData = movieHistory.find((movie) => movie.eventType === 'grabbed' && movie.data.guid === guid);
|
||||||
if (historyGrabbedData) {
|
if (historyGrabbedData) {
|
||||||
historyFailedData = movieHistory.find((movie) => movie.eventType === 'downloadFailed' && movie.sourceTitle === historyGrabbedData.sourceTitle);
|
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 {
|
return {
|
||||||
historyGrabbedData,
|
historyGrabbedData,
|
||||||
historyFailedData,
|
historyFailedData,
|
||||||
blacklistData
|
blocklistData
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -38,7 +38,7 @@ class InteractiveSearchRowConnector extends Component {
|
|||||||
const {
|
const {
|
||||||
historyGrabbedData,
|
historyGrabbedData,
|
||||||
historyFailedData,
|
historyFailedData,
|
||||||
blacklistData,
|
blocklistData,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ class InteractiveSearchRowConnector extends Component {
|
|||||||
<InteractiveSearchRow
|
<InteractiveSearchRow
|
||||||
historyGrabbedData={historyGrabbedData}
|
historyGrabbedData={historyGrabbedData}
|
||||||
historyFailedData={historyFailedData}
|
historyFailedData={historyFailedData}
|
||||||
blacklistData={blacklistData}
|
blocklistData={blocklistData}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -56,7 +56,7 @@ class InteractiveSearchRowConnector extends Component {
|
|||||||
InteractiveSearchRowConnector.propTypes = {
|
InteractiveSearchRowConnector.propTypes = {
|
||||||
historyGrabbedData: PropTypes.object,
|
historyGrabbedData: PropTypes.object,
|
||||||
historyFailedData: PropTypes.object,
|
historyFailedData: PropTypes.object,
|
||||||
blacklistData: PropTypes.object
|
blocklistData: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(createMapStateToProps)(InteractiveSearchRowConnector);
|
export default connect(createMapStateToProps)(InteractiveSearchRowConnector);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import * as commandNames from 'Commands/commandNames';
|
|||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
import { clearExtraFiles, fetchExtraFiles } from 'Store/Actions/extraFileActions';
|
import { clearExtraFiles, fetchExtraFiles } from 'Store/Actions/extraFileActions';
|
||||||
import { toggleMovieMonitored } from 'Store/Actions/movieActions';
|
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 { clearMovieCredits, fetchMovieCredits } from 'Store/Actions/movieCreditsActions';
|
||||||
import { clearMovieFiles, fetchMovieFiles } from 'Store/Actions/movieFileActions';
|
import { clearMovieFiles, fetchMovieFiles } from 'Store/Actions/movieFileActions';
|
||||||
import { clearMovieHistory, fetchMovieHistory } from 'Store/Actions/movieHistoryActions';
|
import { clearMovieHistory, fetchMovieHistory } from 'Store/Actions/movieHistoryActions';
|
||||||
@@ -222,11 +222,11 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
onGoToMovie(titleSlug) {
|
onGoToMovie(titleSlug) {
|
||||||
dispatch(push(`${window.Radarr.urlBase}/movie/${titleSlug}`));
|
dispatch(push(`${window.Radarr.urlBase}/movie/${titleSlug}`));
|
||||||
},
|
},
|
||||||
dispatchFetchMovieBlacklist({ movieId }) {
|
dispatchFetchMovieBlocklist({ movieId }) {
|
||||||
dispatch(fetchMovieBlacklist({ movieId }));
|
dispatch(fetchMovieBlocklist({ movieId }));
|
||||||
},
|
},
|
||||||
dispatchClearMovieBlacklist() {
|
dispatchClearMovieBlocklist() {
|
||||||
dispatch(clearMovieBlacklist());
|
dispatch(clearMovieBlocklist());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -280,7 +280,7 @@ class MovieDetailsConnector extends Component {
|
|||||||
const movieId = this.props.id;
|
const movieId = this.props.id;
|
||||||
|
|
||||||
this.props.dispatchFetchMovieFiles({ movieId });
|
this.props.dispatchFetchMovieFiles({ movieId });
|
||||||
this.props.dispatchFetchMovieBlacklist({ movieId });
|
this.props.dispatchFetchMovieBlocklist({ movieId });
|
||||||
this.props.dispatchFetchMovieHistory({ movieId });
|
this.props.dispatchFetchMovieHistory({ movieId });
|
||||||
this.props.dispatchFetchExtraFiles({ movieId });
|
this.props.dispatchFetchExtraFiles({ movieId });
|
||||||
this.props.dispatchFetchMovieCredits({ movieId });
|
this.props.dispatchFetchMovieCredits({ movieId });
|
||||||
@@ -290,7 +290,7 @@ class MovieDetailsConnector extends Component {
|
|||||||
|
|
||||||
unpopulate = () => {
|
unpopulate = () => {
|
||||||
this.props.dispatchCancelFetchReleases();
|
this.props.dispatchCancelFetchReleases();
|
||||||
this.props.dispatchClearMovieBlacklist();
|
this.props.dispatchClearMovieBlocklist();
|
||||||
this.props.dispatchClearMovieFiles();
|
this.props.dispatchClearMovieFiles();
|
||||||
this.props.dispatchClearMovieHistory();
|
this.props.dispatchClearMovieHistory();
|
||||||
this.props.dispatchClearExtraFiles();
|
this.props.dispatchClearExtraFiles();
|
||||||
@@ -362,8 +362,8 @@ MovieDetailsConnector.propTypes = {
|
|||||||
dispatchClearQueueDetails: PropTypes.func.isRequired,
|
dispatchClearQueueDetails: PropTypes.func.isRequired,
|
||||||
dispatchFetchImportListSchema: PropTypes.func.isRequired,
|
dispatchFetchImportListSchema: PropTypes.func.isRequired,
|
||||||
dispatchExecuteCommand: PropTypes.func.isRequired,
|
dispatchExecuteCommand: PropTypes.func.isRequired,
|
||||||
dispatchFetchMovieBlacklist: PropTypes.func.isRequired,
|
dispatchFetchMovieBlocklist: PropTypes.func.isRequired,
|
||||||
dispatchClearMovieBlacklist: PropTypes.func.isRequired,
|
dispatchClearMovieBlocklist: PropTypes.func.isRequired,
|
||||||
onGoToMovie: PropTypes.func.isRequired
|
onGoToMovie: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,19 @@ function MovieDetailsLinks(props) {
|
|||||||
</Label>
|
</Label>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
className={styles.link}
|
||||||
|
to={`https://letterboxd.com/tmdb/${tmdbId}`}
|
||||||
|
>
|
||||||
|
<Label
|
||||||
|
className={styles.linkLabel}
|
||||||
|
kind={kinds.INFO}
|
||||||
|
size={sizes.LARGE}
|
||||||
|
>
|
||||||
|
{translate('Letterboxd')}
|
||||||
|
</Label>
|
||||||
|
</Link>
|
||||||
|
|
||||||
{
|
{
|
||||||
!!imdbId &&
|
!!imdbId &&
|
||||||
<Link
|
<Link
|
||||||
@@ -61,7 +74,7 @@ function MovieDetailsLinks(props) {
|
|||||||
!!imdbId &&
|
!!imdbId &&
|
||||||
<Link
|
<Link
|
||||||
className={styles.link}
|
className={styles.link}
|
||||||
to={` https://moviechat.org/${imdbId}/`}
|
to={`https://moviechat.org/${imdbId}/`}
|
||||||
>
|
>
|
||||||
<Label
|
<Label
|
||||||
className={styles.linkLabel}
|
className={styles.linkLabel}
|
||||||
@@ -77,7 +90,7 @@ function MovieDetailsLinks(props) {
|
|||||||
!!youTubeTrailerId &&
|
!!youTubeTrailerId &&
|
||||||
<Link
|
<Link
|
||||||
className={styles.link}
|
className={styles.link}
|
||||||
to={` https://www.youtube.com/watch?v=${youTubeTrailerId}/`}
|
to={`https://www.youtube.com/watch?v=${youTubeTrailerId}/`}
|
||||||
>
|
>
|
||||||
<Label
|
<Label
|
||||||
className={styles.linkLabel}
|
className={styles.linkLabel}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
background-color: $dangerColor;
|
background-color: $dangerColor;
|
||||||
|
|
||||||
&:global(.colorImpaired) {
|
&:global(.colorImpaired) {
|
||||||
background: repeating-linear-gradient(90deg, color($dangerColor shade(5%)), color($dangerColor shade(5%)) 5px, color($dangerColor shade(15%)) 5px, color($dangerColor shade(15%)) 10px);
|
background: repeating-linear-gradient(90deg, $dangerColor, $dangerColor 5px, $dangerColor 5px, $dimColor 10px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
background-color: $warningColor;
|
background-color: $warningColor;
|
||||||
|
|
||||||
&:global(.colorImpaired) {
|
&:global(.colorImpaired) {
|
||||||
background: repeating-linear-gradient(45deg, $warningColor, $warningColor 5px, color($warningColor tint(15%)) 5px, color($warningColor tint(15%)) 10px);
|
background: repeating-linear-gradient(45deg, $warningColor, $warningColor 5px, $warningColor 5px, $dimColor 10px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,8 @@ class MovieIndexOverviews extends Component {
|
|||||||
columnCount: 1,
|
columnCount: 1,
|
||||||
posterWidth: 162,
|
posterWidth: 162,
|
||||||
posterHeight: 238,
|
posterHeight: 238,
|
||||||
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {})
|
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {}),
|
||||||
|
scrollRestored: false
|
||||||
};
|
};
|
||||||
|
|
||||||
this._grid = null;
|
this._grid = null;
|
||||||
@@ -72,13 +73,15 @@ class MovieIndexOverviews extends Component {
|
|||||||
sortKey,
|
sortKey,
|
||||||
overviewOptions,
|
overviewOptions,
|
||||||
jumpToCharacter,
|
jumpToCharacter,
|
||||||
|
scrollTop,
|
||||||
isMovieEditorActive,
|
isMovieEditorActive,
|
||||||
isSmallScreen
|
isSmallScreen
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
width,
|
width,
|
||||||
rowHeight
|
rowHeight,
|
||||||
|
scrollRestored
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
if (prevProps.sortKey !== sortKey ||
|
if (prevProps.sortKey !== sortKey ||
|
||||||
@@ -97,6 +100,11 @@ class MovieIndexOverviews extends Component {
|
|||||||
this._grid.recomputeGridSize();
|
this._grid.recomputeGridSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._grid && scrollTop !== 0 && !scrollRestored) {
|
||||||
|
this.setState({ scrollRestored: true });
|
||||||
|
this._grid.scrollToPosition({ scrollTop });
|
||||||
|
}
|
||||||
|
|
||||||
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
|
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
|
||||||
const index = getIndexOfFirstCharacter(items, jumpToCharacter);
|
const index = getIndexOfFirstCharacter(items, jumpToCharacter);
|
||||||
|
|
||||||
@@ -262,6 +270,7 @@ MovieIndexOverviews.propTypes = {
|
|||||||
sortKey: PropTypes.string,
|
sortKey: PropTypes.string,
|
||||||
overviewOptions: PropTypes.object.isRequired,
|
overviewOptions: PropTypes.object.isRequired,
|
||||||
jumpToCharacter: PropTypes.string,
|
jumpToCharacter: PropTypes.string,
|
||||||
|
scrollTop: PropTypes.number.isRequired,
|
||||||
scroller: PropTypes.instanceOf(Element).isRequired,
|
scroller: PropTypes.instanceOf(Element).isRequired,
|
||||||
showRelativeDates: PropTypes.bool.isRequired,
|
showRelativeDates: PropTypes.bool.isRequired,
|
||||||
shortDateFormat: PropTypes.string.isRequired,
|
shortDateFormat: PropTypes.string.isRequired,
|
||||||
|
|||||||
@@ -104,7 +104,8 @@ class MovieIndexPosters extends Component {
|
|||||||
columnCount: 1,
|
columnCount: 1,
|
||||||
posterWidth: 162,
|
posterWidth: 162,
|
||||||
posterHeight: 238,
|
posterHeight: 238,
|
||||||
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {})
|
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {}),
|
||||||
|
scrollRestored: false
|
||||||
};
|
};
|
||||||
|
|
||||||
this._isInitialized = false;
|
this._isInitialized = false;
|
||||||
@@ -119,14 +120,16 @@ class MovieIndexPosters extends Component {
|
|||||||
posterOptions,
|
posterOptions,
|
||||||
jumpToCharacter,
|
jumpToCharacter,
|
||||||
isSmallScreen,
|
isSmallScreen,
|
||||||
isMovieEditorActive
|
isMovieEditorActive,
|
||||||
|
scrollTop
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
width,
|
width,
|
||||||
columnWidth,
|
columnWidth,
|
||||||
columnCount,
|
columnCount,
|
||||||
rowHeight
|
rowHeight,
|
||||||
|
scrollRestored
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
if (prevProps.sortKey !== sortKey ||
|
if (prevProps.sortKey !== sortKey ||
|
||||||
@@ -145,6 +148,11 @@ class MovieIndexPosters extends Component {
|
|||||||
this._grid.recomputeGridSize();
|
this._grid.recomputeGridSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._grid && scrollTop !== 0 && !scrollRestored) {
|
||||||
|
this.setState({ scrollRestored: true });
|
||||||
|
this._grid.scrollToPosition({ scrollTop });
|
||||||
|
}
|
||||||
|
|
||||||
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
|
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
|
||||||
const index = getIndexOfFirstCharacter(items, jumpToCharacter);
|
const index = getIndexOfFirstCharacter(items, jumpToCharacter);
|
||||||
|
|
||||||
@@ -157,6 +165,10 @@ class MovieIndexPosters extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._grid && scrollTop !== 0) {
|
||||||
|
this._grid.scrollToPosition({ scrollTop });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -332,6 +344,7 @@ MovieIndexPosters.propTypes = {
|
|||||||
sortKey: PropTypes.string,
|
sortKey: PropTypes.string,
|
||||||
posterOptions: PropTypes.object.isRequired,
|
posterOptions: PropTypes.object.isRequired,
|
||||||
jumpToCharacter: PropTypes.string,
|
jumpToCharacter: PropTypes.string,
|
||||||
|
scrollTop: PropTypes.number.isRequired,
|
||||||
scroller: PropTypes.instanceOf(Element).isRequired,
|
scroller: PropTypes.instanceOf(Element).isRequired,
|
||||||
showRelativeDates: PropTypes.bool.isRequired,
|
showRelativeDates: PropTypes.bool.isRequired,
|
||||||
shortDateFormat: PropTypes.string.isRequired,
|
shortDateFormat: PropTypes.string.isRequired,
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import PropTypes from 'prop-types';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ProgressBar from 'Components/ProgressBar';
|
import ProgressBar from 'Components/ProgressBar';
|
||||||
import { sizes } from 'Helpers/Props';
|
import { sizes } from 'Helpers/Props';
|
||||||
import getProgressBarKind from 'Utilities/Movie/getProgressBarKind';
|
|
||||||
import getQueueStatusText from 'Utilities/Movie/getQueueStatusText';
|
import getQueueStatusText from 'Utilities/Movie/getQueueStatusText';
|
||||||
|
import getStatusStyle from 'Utilities/Movie/getStatusStyle';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import styles from './MovieIndexProgressBar.css';
|
import styles from './MovieIndexProgressBar.css';
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ function MovieIndexProgressBar(props) {
|
|||||||
className={styles.progressBar}
|
className={styles.progressBar}
|
||||||
containerClassName={styles.progress}
|
containerClassName={styles.progress}
|
||||||
progress={progress}
|
progress={progress}
|
||||||
kind={getProgressBarKind(status, monitored, hasFile, isAvailable, queueStatusText)}
|
kind={getStatusStyle(status, monitored, hasFile, isAvailable, 'kinds', queueStatusText)}
|
||||||
size={detailedProgressBar ? sizes.MEDIUM : sizes.SMALL}
|
size={detailedProgressBar ? sizes.MEDIUM : sizes.SMALL}
|
||||||
showText={detailedProgressBar}
|
showText={detailedProgressBar}
|
||||||
width={posterWidth}
|
width={posterWidth}
|
||||||
|
|||||||
@@ -45,11 +45,16 @@
|
|||||||
flex: 0 0 100px;
|
flex: 0 0 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movieStatus,
|
.movieStatus {
|
||||||
|
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||||
|
|
||||||
|
flex: 0 0 110px;
|
||||||
|
}
|
||||||
|
|
||||||
.certification {
|
.certification {
|
||||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||||
|
|
||||||
flex: 0 0 100px;
|
flex: 0 0 90px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.year {
|
.year {
|
||||||
|
|||||||
@@ -52,11 +52,16 @@
|
|||||||
flex: 0 0 100px;
|
flex: 0 0 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movieStatus,
|
.movieStatus {
|
||||||
|
composes: cell;
|
||||||
|
|
||||||
|
flex: 0 0 110px;
|
||||||
|
}
|
||||||
|
|
||||||
.certification {
|
.certification {
|
||||||
composes: cell;
|
composes: cell;
|
||||||
|
|
||||||
flex: 0 0 100px;
|
flex: 0 0 90px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.year {
|
.year {
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ class MovieIndexTable extends Component {
|
|||||||
isSmallScreen,
|
isSmallScreen,
|
||||||
onSortPress,
|
onSortPress,
|
||||||
scroller,
|
scroller,
|
||||||
|
scrollTop,
|
||||||
allSelected,
|
allSelected,
|
||||||
allUnselected,
|
allUnselected,
|
||||||
onSelectAllChange,
|
onSelectAllChange,
|
||||||
@@ -100,6 +101,7 @@ class MovieIndexTable extends Component {
|
|||||||
items={items}
|
items={items}
|
||||||
scrollIndex={this.state.scrollIndex}
|
scrollIndex={this.state.scrollIndex}
|
||||||
isSmallScreen={isSmallScreen}
|
isSmallScreen={isSmallScreen}
|
||||||
|
scrollTop={scrollTop}
|
||||||
scroller={scroller}
|
scroller={scroller}
|
||||||
rowHeight={38}
|
rowHeight={38}
|
||||||
overscanRowCount={2}
|
overscanRowCount={2}
|
||||||
@@ -130,6 +132,7 @@ MovieIndexTable.propTypes = {
|
|||||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
sortDirection: PropTypes.oneOf(sortDirections.all),
|
||||||
jumpToCharacter: PropTypes.string,
|
jumpToCharacter: PropTypes.string,
|
||||||
isSmallScreen: PropTypes.bool.isRequired,
|
isSmallScreen: PropTypes.bool.isRequired,
|
||||||
|
scrollTop: PropTypes.number,
|
||||||
scroller: PropTypes.instanceOf(Element).isRequired,
|
scroller: PropTypes.instanceOf(Element).isRequired,
|
||||||
onSortPress: PropTypes.func.isRequired,
|
onSortPress: PropTypes.func.isRequired,
|
||||||
allSelected: PropTypes.bool.isRequired,
|
allSelected: PropTypes.bool.isRequired,
|
||||||
|
|||||||
@@ -2,3 +2,45 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
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 PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
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 getQueueStatusText from 'Utilities/Movie/getQueueStatusText';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import styles from './MovieFileStatus.css';
|
import styles from './MovieFileStatus.css';
|
||||||
@@ -13,7 +11,8 @@ function MovieFileStatus(props) {
|
|||||||
monitored,
|
monitored,
|
||||||
movieFile,
|
movieFile,
|
||||||
queueStatus,
|
queueStatus,
|
||||||
queueState
|
queueState,
|
||||||
|
colorImpairedMode
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const hasMovieFile = !!movieFile;
|
const hasMovieFile = !!movieFile;
|
||||||
@@ -24,12 +23,8 @@ function MovieFileStatus(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.center}>
|
<div className={styles.center}>
|
||||||
<Label
|
<span className={styles.queue} />
|
||||||
title={queueStatusText}
|
{queueStatusText}
|
||||||
kind={kinds.QUEUE}
|
|
||||||
>
|
|
||||||
{queueStatusText}
|
|
||||||
</Label>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -39,51 +34,44 @@ function MovieFileStatus(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.center}>
|
<div className={styles.center}>
|
||||||
<MovieQuality
|
<span className={styles.ended} />
|
||||||
title={quality.quality.name}
|
{quality.quality.name}
|
||||||
size={movieFile.size}
|
|
||||||
quality={quality}
|
|
||||||
isMonitored={monitored}
|
|
||||||
isCutoffNotMet={movieFile.qualityCutoffNotMet}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!monitored) {
|
if (!monitored) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.center}>
|
<div className={classNames(
|
||||||
<Label
|
styles.center,
|
||||||
title={translate('NotMonitored')}
|
styles.missingUnmonitoredBackground,
|
||||||
kind={kinds.WARNING}
|
colorImpairedMode && 'colorImpaired'
|
||||||
>
|
)}
|
||||||
{translate('NotMonitored')}
|
>
|
||||||
</Label>
|
<span className={styles.missingUnmonitored} />
|
||||||
|
{translate('NotMonitored')}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasReleased) {
|
if (hasReleased) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.center}>
|
<div className={classNames(
|
||||||
<Label
|
styles.center,
|
||||||
title={translate('MovieAvailableButMissing')}
|
styles.missingMonitoredBackground,
|
||||||
kind={kinds.DANGER}
|
colorImpairedMode && 'colorImpaired'
|
||||||
>
|
)}
|
||||||
{translate('Missing')}
|
>
|
||||||
</Label>
|
<span className={styles.missingMonitored} />
|
||||||
|
{translate('Missing')}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.center}>
|
<div className={styles.center}>
|
||||||
<Label
|
<span className={styles.continuing} />
|
||||||
title={translate('NotAvailable')}
|
{translate('NotAvailable')}
|
||||||
kind={kinds.INFO}
|
|
||||||
>
|
|
||||||
{translate('NotAvailable')}
|
|
||||||
</Label>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -93,7 +81,8 @@ MovieFileStatus.propTypes = {
|
|||||||
monitored: PropTypes.bool.isRequired,
|
monitored: PropTypes.bool.isRequired,
|
||||||
movieFile: PropTypes.object,
|
movieFile: PropTypes.object,
|
||||||
queueStatus: PropTypes.string,
|
queueStatus: PropTypes.string,
|
||||||
queueState: PropTypes.string
|
queueState: PropTypes.string,
|
||||||
|
colorImpairedMode: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MovieFileStatus;
|
export default MovieFileStatus;
|
||||||
|
|||||||
@@ -3,18 +3,21 @@ import React, { Component } from 'react';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import createMovieSelector from 'Store/Selectors/createMovieSelector';
|
import createMovieSelector from 'Store/Selectors/createMovieSelector';
|
||||||
|
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||||
import MovieFileStatus from './MovieFileStatus';
|
import MovieFileStatus from './MovieFileStatus';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createMovieSelector(),
|
createMovieSelector(),
|
||||||
(movie) => {
|
createUISettingsSelector(),
|
||||||
|
(movie, uiSettings) => {
|
||||||
return {
|
return {
|
||||||
inCinemas: movie.inCinemas,
|
inCinemas: movie.inCinemas,
|
||||||
isAvailable: movie.isAvailable,
|
isAvailable: movie.isAvailable,
|
||||||
monitored: movie.monitored,
|
monitored: movie.monitored,
|
||||||
grabbed: movie.grabbed,
|
grabbed: movie.grabbed,
|
||||||
movieFile: movie.movieFile
|
movieFile: movie.movieFile,
|
||||||
|
colorImpairedMode: uiSettings.enableColorImpairedMode
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ class FileEditModalContent extends Component {
|
|||||||
<FormLabel>{translate('Languages')}</FormLabel>
|
<FormLabel>{translate('Languages')}</FormLabel>
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.SELECT}
|
type={inputTypes.LANGUAGE_SELECT}
|
||||||
name="languageIds"
|
name="languageIds"
|
||||||
value={languageIds}
|
value={languageIds}
|
||||||
values={languageOptions}
|
values={languageOptions}
|
||||||
|
|||||||
+2
-2
@@ -6,10 +6,10 @@ import { fetchCustomFormatSpecifications } from 'Store/Actions/settingsActions';
|
|||||||
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
||||||
import ExportCustomFormatModalContent from './ExportCustomFormatModalContent';
|
import ExportCustomFormatModalContent from './ExportCustomFormatModalContent';
|
||||||
|
|
||||||
const blacklistedProperties = ['id', 'implementationName', 'infoLink'];
|
const omittedProperties = ['id', 'implementationName', 'infoLink'];
|
||||||
|
|
||||||
function replacer(key, value) {
|
function replacer(key, value) {
|
||||||
if (blacklistedProperties.includes(key)) {
|
if (omittedProperties.includes(key)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -57,7 +57,7 @@ class AddSpecificationModalContent extends Component {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{translate('VisitGithubCustomFormatsAphrodite')}
|
{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>
|
</div>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
|
|||||||
@@ -24,10 +24,7 @@ const requiresRestartKeys = [
|
|||||||
'enableSsl',
|
'enableSsl',
|
||||||
'sslPort',
|
'sslPort',
|
||||||
'sslCertPath',
|
'sslCertPath',
|
||||||
'sslCertPassword',
|
'sslCertPassword'
|
||||||
'authenticationMethod',
|
|
||||||
'username',
|
|
||||||
'password'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
class GeneralSettings extends Component {
|
class GeneralSettings extends Component {
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ function HostSettings(props) {
|
|||||||
name="port"
|
name="port"
|
||||||
min={1}
|
min={1}
|
||||||
max={65535}
|
max={65535}
|
||||||
|
autocomplete="off"
|
||||||
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
|
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...port}
|
{...port}
|
||||||
|
|||||||
@@ -86,7 +86,6 @@ class SecuritySettings extends Component {
|
|||||||
name="authenticationMethod"
|
name="authenticationMethod"
|
||||||
values={authenticationMethodOptions}
|
values={authenticationMethodOptions}
|
||||||
helpText={translate('AuthenticationMethodHelpText')}
|
helpText={translate('AuthenticationMethodHelpText')}
|
||||||
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
|
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...authenticationMethod}
|
{...authenticationMethod}
|
||||||
/>
|
/>
|
||||||
@@ -100,7 +99,6 @@ class SecuritySettings extends Component {
|
|||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.TEXT}
|
type={inputTypes.TEXT}
|
||||||
name="username"
|
name="username"
|
||||||
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
|
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...username}
|
{...username}
|
||||||
/>
|
/>
|
||||||
@@ -115,7 +113,6 @@ class SecuritySettings extends Component {
|
|||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.PASSWORD}
|
type={inputTypes.PASSWORD}
|
||||||
name="password"
|
name="password"
|
||||||
helpTextWarning={translate('RestartRequiredHelpTextWarning')}
|
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...password}
|
{...password}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ function UpdateSettings(props) {
|
|||||||
type={inputTypes.TEXT}
|
type={inputTypes.TEXT}
|
||||||
name="branch"
|
name="branch"
|
||||||
helpText={usingExternalUpdateMechanism ? translate('BranchUpdateMechanism') : translate('BranchUpdate')}
|
helpText={usingExternalUpdateMechanism ? translate('BranchUpdateMechanism') : translate('BranchUpdate')}
|
||||||
helpLink="https://wiki.servarr.com/Radarr_Settings#Updates"
|
helpLink="https://wiki.servarr.com/radarr/settings#updates"
|
||||||
{...branch}
|
{...branch}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
readOnly={usingExternalUpdateMechanism}
|
readOnly={usingExternalUpdateMechanism}
|
||||||
@@ -92,7 +92,7 @@ function UpdateSettings(props) {
|
|||||||
name="updateMechanism"
|
name="updateMechanism"
|
||||||
values={updateOptions}
|
values={updateOptions}
|
||||||
helpText={translate('UpdateMechanismHelpText')}
|
helpText={translate('UpdateMechanismHelpText')}
|
||||||
helpLink="https://wiki.servarr.com/Radarr_Settings#Updates"
|
helpLink="https://wiki.servarr.com/radarr/settings#updates"
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...updateMechanism}
|
{...updateMechanism}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import Alert from 'Components/Alert';
|
||||||
import Form from 'Components/Form/Form';
|
import Form from 'Components/Form/Form';
|
||||||
import FormGroup from 'Components/Form/FormGroup';
|
import FormGroup from 'Components/Form/FormGroup';
|
||||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||||
@@ -47,7 +48,8 @@ function EditImportListModalContent(props) {
|
|||||||
rootFolderPath,
|
rootFolderPath,
|
||||||
searchOnAdd,
|
searchOnAdd,
|
||||||
tags,
|
tags,
|
||||||
fields
|
fields,
|
||||||
|
message
|
||||||
} = item;
|
} = item;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -74,6 +76,15 @@ function EditImportListModalContent(props) {
|
|||||||
<Form
|
<Form
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
>
|
>
|
||||||
|
{
|
||||||
|
!!message &&
|
||||||
|
<Alert
|
||||||
|
className={styles.message}
|
||||||
|
kind={message.value.type}
|
||||||
|
>
|
||||||
|
{message.value.message}
|
||||||
|
</Alert>
|
||||||
|
}
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<FormLabel>{translate('Name')}</FormLabel>
|
<FormLabel>{translate('Name')}</FormLabel>
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ function EditIndexerModalContent(props) {
|
|||||||
enableInteractiveSearch,
|
enableInteractiveSearch,
|
||||||
supportsRss,
|
supportsRss,
|
||||||
supportsSearch,
|
supportsSearch,
|
||||||
|
tags,
|
||||||
fields,
|
fields,
|
||||||
priority
|
priority
|
||||||
} = item;
|
} = item;
|
||||||
@@ -135,6 +136,7 @@ function EditIndexerModalContent(props) {
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
<FormGroup
|
<FormGroup
|
||||||
advancedSettings={advancedSettings}
|
advancedSettings={advancedSettings}
|
||||||
isAdvanced={true}
|
isAdvanced={true}
|
||||||
@@ -151,6 +153,18 @@ function EditIndexerModalContent(props) {
|
|||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>Tags</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.TAG}
|
||||||
|
name="tags"
|
||||||
|
helpText={translate('IndexerTagHelpText')}
|
||||||
|
{...tags}
|
||||||
|
onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
</Form>
|
</Form>
|
||||||
}
|
}
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Card from 'Components/Card';
|
|||||||
import Label from 'Components/Label';
|
import Label from 'Components/Label';
|
||||||
import IconButton from 'Components/Link/IconButton';
|
import IconButton from 'Components/Link/IconButton';
|
||||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||||
|
import TagList from 'Components/TagList';
|
||||||
import { icons, kinds } from 'Helpers/Props';
|
import { icons, kinds } from 'Helpers/Props';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import EditIndexerModalConnector from './EditIndexerModalConnector';
|
import EditIndexerModalConnector from './EditIndexerModalConnector';
|
||||||
@@ -68,6 +69,8 @@ class Indexer extends Component {
|
|||||||
enableRss,
|
enableRss,
|
||||||
enableAutomaticSearch,
|
enableAutomaticSearch,
|
||||||
enableInteractiveSearch,
|
enableInteractiveSearch,
|
||||||
|
tags,
|
||||||
|
tagList,
|
||||||
supportsRss,
|
supportsRss,
|
||||||
supportsSearch,
|
supportsSearch,
|
||||||
priority,
|
priority,
|
||||||
@@ -133,6 +136,11 @@ class Indexer extends Component {
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<TagList
|
||||||
|
tags={tags}
|
||||||
|
tagList={tagList}
|
||||||
|
/>
|
||||||
|
|
||||||
<EditIndexerModalConnector
|
<EditIndexerModalConnector
|
||||||
id={id}
|
id={id}
|
||||||
isOpen={this.state.isEditIndexerModalOpen}
|
isOpen={this.state.isEditIndexerModalOpen}
|
||||||
@@ -160,6 +168,8 @@ Indexer.propTypes = {
|
|||||||
enableRss: PropTypes.bool.isRequired,
|
enableRss: PropTypes.bool.isRequired,
|
||||||
enableAutomaticSearch: PropTypes.bool.isRequired,
|
enableAutomaticSearch: PropTypes.bool.isRequired,
|
||||||
enableInteractiveSearch: PropTypes.bool.isRequired,
|
enableInteractiveSearch: PropTypes.bool.isRequired,
|
||||||
|
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
|
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
supportsRss: PropTypes.bool.isRequired,
|
supportsRss: PropTypes.bool.isRequired,
|
||||||
supportsSearch: PropTypes.bool.isRequired,
|
supportsSearch: PropTypes.bool.isRequired,
|
||||||
onCloneIndexerPress: PropTypes.func.isRequired,
|
onCloneIndexerPress: PropTypes.func.isRequired,
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ class Indexers extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
|
tagList,
|
||||||
dispatchCloneIndexer,
|
dispatchCloneIndexer,
|
||||||
onConfirmDeleteIndexer,
|
onConfirmDeleteIndexer,
|
||||||
...otherProps
|
...otherProps
|
||||||
@@ -79,6 +80,7 @@ class Indexers extends Component {
|
|||||||
<Indexer
|
<Indexer
|
||||||
key={item.id}
|
key={item.id}
|
||||||
{...item}
|
{...item}
|
||||||
|
tagList={tagList}
|
||||||
showPriority={showPriority}
|
showPriority={showPriority}
|
||||||
onCloneIndexerPress={this.onCloneIndexerPress}
|
onCloneIndexerPress={this.onCloneIndexerPress}
|
||||||
onConfirmDeleteIndexer={onConfirmDeleteIndexer}
|
onConfirmDeleteIndexer={onConfirmDeleteIndexer}
|
||||||
@@ -119,6 +121,7 @@ Indexers.propTypes = {
|
|||||||
isFetching: PropTypes.bool.isRequired,
|
isFetching: PropTypes.bool.isRequired,
|
||||||
error: PropTypes.object,
|
error: PropTypes.object,
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
dispatchCloneIndexer: PropTypes.func.isRequired,
|
dispatchCloneIndexer: PropTypes.func.isRequired,
|
||||||
onConfirmDeleteIndexer: PropTypes.func.isRequired
|
onConfirmDeleteIndexer: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,13 +4,20 @@ import { connect } from 'react-redux';
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { cloneIndexer, deleteIndexer, fetchIndexers } from 'Store/Actions/settingsActions';
|
import { cloneIndexer, deleteIndexer, fetchIndexers } from 'Store/Actions/settingsActions';
|
||||||
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
||||||
|
import createTagsSelector from 'Store/Selectors/createTagsSelector';
|
||||||
import sortByName from 'Utilities/Array/sortByName';
|
import sortByName from 'Utilities/Array/sortByName';
|
||||||
import Indexers from './Indexers';
|
import Indexers from './Indexers';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createSortedSectionSelector('settings.indexers', sortByName),
|
createSortedSectionSelector('settings.indexers', sortByName),
|
||||||
(indexers) => indexers
|
createTagsSelector(),
|
||||||
|
(indexers, tagList) => {
|
||||||
|
return {
|
||||||
|
...indexers,
|
||||||
|
tagList
|
||||||
|
};
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ function IndexerOptions(props) {
|
|||||||
unit="minutes"
|
unit="minutes"
|
||||||
helpText={translate('HelpText')}
|
helpText={translate('HelpText')}
|
||||||
helpTextWarning={translate('RSSSyncIntervalHelpTextWarning')}
|
helpTextWarning={translate('RSSSyncIntervalHelpTextWarning')}
|
||||||
helpLink="https://wiki.servarr.com/Radarr_FAQ#How_does_Radarr_work"
|
helpLink="https://wiki.servarr.com/radarr/faq#how-does-radarr-work"
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...settings.rssSyncInterval}
|
{...settings.rssSyncInterval}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -115,10 +115,10 @@ class NamingModal extends Component {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const movieTokens = [
|
const movieTokens = [
|
||||||
{ token: '{Movie Title}', example: 'Movie Title!' },
|
{ token: '{Movie Title}', example: 'Movie\'s Title' },
|
||||||
{ token: '{Movie Title:DE}', example: 'Filetitle' },
|
{ token: '{Movie Title:DE}', example: 'Filetitle' },
|
||||||
{ token: '{Movie CleanTitle}', example: 'Movie Title' },
|
{ token: '{Movie CleanTitle}', example: 'Movies Title' },
|
||||||
{ token: '{Movie TitleThe}', example: 'Movie Title, The' },
|
{ token: '{Movie TitleThe}', example: 'Movie\'s Title, The' },
|
||||||
{ token: '{Movie OriginalTitle}', example: 'Τίτλος ταινίας' },
|
{ token: '{Movie OriginalTitle}', example: 'Τίτλος ταινίας' },
|
||||||
{ token: '{Movie TitleFirstCharacter}', example: 'M' },
|
{ token: '{Movie TitleFirstCharacter}', example: 'M' },
|
||||||
{ token: '{Movie Collection}', example: 'The Movie Collection' },
|
{ token: '{Movie Collection}', example: 'The Movie Collection' },
|
||||||
@@ -132,8 +132,8 @@ class NamingModal extends Component {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const qualityTokens = [
|
const qualityTokens = [
|
||||||
{ token: '{Quality Full}', example: 'HDTV 720p Proper' },
|
{ token: '{Quality Full}', example: 'HDTV-720p Proper' },
|
||||||
{ token: '{Quality Title}', example: 'HDTV 720p' }
|
{ token: '{Quality Title}', example: 'HDTV-720p' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const mediaInfoTokens = [
|
const mediaInfoTokens = [
|
||||||
@@ -164,7 +164,7 @@ class NamingModal extends Component {
|
|||||||
|
|
||||||
const originalTokens = [
|
const originalTokens = [
|
||||||
{ token: '{Original Title}', example: 'Movie.Title.HDTV.x264-EVOLVE' },
|
{ token: '{Original Title}', example: 'Movie.Title.HDTV.x264-EVOLVE' },
|
||||||
{ token: '{Original Filename}', example: 'Movie.title.hdtv.x264-EVOLVE' }
|
{ token: '{Original Filename}', example: 'movie title hdtv.x264-Evolve' }
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ export const certificationCountryOptions = [
|
|||||||
{ key: 'gb', value: 'Great Britain' },
|
{ key: 'gb', value: 'Great Britain' },
|
||||||
{ key: 'it', value: 'Italy' },
|
{ key: 'it', value: 'Italy' },
|
||||||
{ key: 'es', value: 'Spain' },
|
{ key: 'es', value: 'Spain' },
|
||||||
{ key: 'us', value: 'United States' }
|
{ key: 'us', value: 'United States' },
|
||||||
|
{ key: 'nz', value: 'New Zealand' }
|
||||||
];
|
];
|
||||||
|
|
||||||
function MetadataOptions(props) {
|
function MetadataOptions(props) {
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ function NotificationEventItems(props) {
|
|||||||
<FormLabel>{translate('NotificationTriggers')}</FormLabel>
|
<FormLabel>{translate('NotificationTriggers')}</FormLabel>
|
||||||
<div>
|
<div>
|
||||||
<FormInputHelpText
|
<FormInputHelpText
|
||||||
text={translate('NotifcationTriggersHelpText')}
|
text={translate('NotificationTriggersHelpText')}
|
||||||
link="https://wiki.servarr.com/Radarr_Settings#Connections"
|
link="https://wiki.servarr.com/radarr/settings#connections"
|
||||||
/>
|
/>
|
||||||
<div className={styles.events}>
|
<div className={styles.events}>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ function EditDelayProfileModalContent(props) {
|
|||||||
enableTorrent,
|
enableTorrent,
|
||||||
usenetDelay,
|
usenetDelay,
|
||||||
torrentDelay,
|
torrentDelay,
|
||||||
|
bypassIfHighestQuality,
|
||||||
tags
|
tags
|
||||||
} = item;
|
} = item;
|
||||||
|
|
||||||
@@ -110,6 +111,20 @@ function EditDelayProfileModalContent(props) {
|
|||||||
</FormGroup>
|
</FormGroup>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>{translate('BypassDelayIfHighestQuality')}</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.CHECK}
|
||||||
|
name="bypassIfHighestQuality"
|
||||||
|
{...bypassIfHighestQuality}
|
||||||
|
helpText={translate('BypassDelayIfHighestQualityHelpText')}
|
||||||
|
onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
id === 1 ?
|
id === 1 ?
|
||||||
<Alert>
|
<Alert>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user