1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-04-18 21:35:51 -04:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Qstick 982e3c14d3 Fix Tests from Site Image Changes 2021-02-02 22:36:32 -05:00
2112 changed files with 46774 additions and 67757 deletions
+10 -16
View File
@@ -19,10 +19,10 @@ indent_size = 4
dotnet_sort_system_directives_first = true dotnet_sort_system_directives_first = true
# Avoid "this." and "Me." if not necessary # Avoid "this." and "Me." if not necessary
dotnet_style_qualification_for_field = false:warning dotnet_style_qualification_for_field = false:refactoring
dotnet_style_qualification_for_property = false:warning dotnet_style_qualification_for_property = false:refactoring
dotnet_style_qualification_for_method = false:warning dotnet_style_qualification_for_method = false:refactoring
dotnet_style_qualification_for_event = false:warning dotnet_style_qualification_for_event = false:refactoring
# Indentation preferences # Indentation preferences
csharp_indent_block_contents = true csharp_indent_block_contents = true
@@ -32,6 +32,10 @@ csharp_indent_case_contents_when_block = true
csharp_indent_switch_labels = true csharp_indent_switch_labels = true
csharp_indent_labels = flush_left csharp_indent_labels = flush_left
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_event = false:suggestion
dotnet_naming_style.instance_field_style.capitalization = camel_case dotnet_naming_style.instance_field_style.capitalization = camel_case
dotnet_naming_style.instance_field_style.required_prefix = _ dotnet_naming_style.instance_field_style.required_prefix = _
@@ -42,6 +46,7 @@ csharp_style_var_elsewhere = true:suggestion
# Stylecop Rules # Stylecop Rules
dotnet_diagnostic.SA0001.severity = none dotnet_diagnostic.SA0001.severity = none
dotnet_diagnostic.SA1005.severity = none
dotnet_diagnostic.SA1025.severity = none dotnet_diagnostic.SA1025.severity = none
dotnet_diagnostic.SA1101.severity = none dotnet_diagnostic.SA1101.severity = none
dotnet_diagnostic.SA1116.severity = none dotnet_diagnostic.SA1116.severity = none
@@ -105,13 +110,13 @@ dotnet_diagnostic.SA1643.severity = none
dotnet_diagnostic.SA1648.severity = none dotnet_diagnostic.SA1648.severity = none
dotnet_diagnostic.SA1649.severity = none dotnet_diagnostic.SA1649.severity = none
dotnet_diagnostic.SA1651.severity = none dotnet_diagnostic.SA1651.severity = none
dotnet_diagnostic.SX1101.severity = warning
dotnet_diagnostic.SX1309.severity = warning dotnet_diagnostic.SX1309.severity = warning
# Microsoft Analyzers that fail and need to be sorted thru # Microsoft Analyzers that fail and need to be sorted thru
dotnet_diagnostic.ASP0000.severity = suggestion dotnet_diagnostic.ASP0000.severity = suggestion
dotnet_diagnostic.CA1000.severity = suggestion dotnet_diagnostic.CA1000.severity = suggestion
dotnet_diagnostic.CA1001.severity = suggestion dotnet_diagnostic.CA1001.severity = suggestion
dotnet_diagnostic.CA1002.severity = suggestion
dotnet_diagnostic.CA1003.severity = suggestion dotnet_diagnostic.CA1003.severity = suggestion
dotnet_diagnostic.CA1008.severity = suggestion dotnet_diagnostic.CA1008.severity = suggestion
dotnet_diagnostic.CA1010.severity = suggestion dotnet_diagnostic.CA1010.severity = suggestion
@@ -158,16 +163,10 @@ dotnet_diagnostic.CA1304.severity = suggestion
dotnet_diagnostic.CA1305.severity = suggestion dotnet_diagnostic.CA1305.severity = suggestion
dotnet_diagnostic.CA1307.severity = suggestion dotnet_diagnostic.CA1307.severity = suggestion
dotnet_diagnostic.CA1308.severity = suggestion dotnet_diagnostic.CA1308.severity = suggestion
dotnet_diagnostic.CA1309.severity = suggestion
dotnet_diagnostic.CA1310.severity = suggestion
dotnet_diagnostic.CA1401.severity = suggestion dotnet_diagnostic.CA1401.severity = suggestion
dotnet_diagnostic.CA1416.severity = suggestion
dotnet_diagnostic.CA1507.severity = suggestion dotnet_diagnostic.CA1507.severity = suggestion
dotnet_diagnostic.CA1508.severity = suggestion
dotnet_diagnostic.CA1707.severity = suggestion dotnet_diagnostic.CA1707.severity = suggestion
dotnet_diagnostic.CA1708.severity = suggestion
dotnet_diagnostic.CA1710.severity = suggestion dotnet_diagnostic.CA1710.severity = suggestion
dotnet_diagnostic.CA1711.severity = suggestion
dotnet_diagnostic.CA1712.severity = suggestion dotnet_diagnostic.CA1712.severity = suggestion
dotnet_diagnostic.CA1714.severity = suggestion dotnet_diagnostic.CA1714.severity = suggestion
dotnet_diagnostic.CA1715.severity = suggestion dotnet_diagnostic.CA1715.severity = suggestion
@@ -176,14 +175,12 @@ dotnet_diagnostic.CA1717.severity = suggestion
dotnet_diagnostic.CA1720.severity = suggestion dotnet_diagnostic.CA1720.severity = suggestion
dotnet_diagnostic.CA1721.severity = suggestion dotnet_diagnostic.CA1721.severity = suggestion
dotnet_diagnostic.CA1724.severity = suggestion dotnet_diagnostic.CA1724.severity = suggestion
dotnet_diagnostic.CA1725.severity = suggestion
dotnet_diagnostic.CA1801.severity = suggestion dotnet_diagnostic.CA1801.severity = suggestion
dotnet_diagnostic.CA1802.severity = suggestion dotnet_diagnostic.CA1802.severity = suggestion
dotnet_diagnostic.CA1805.severity = suggestion dotnet_diagnostic.CA1805.severity = suggestion
dotnet_diagnostic.CA1806.severity = suggestion dotnet_diagnostic.CA1806.severity = suggestion
dotnet_diagnostic.CA1810.severity = suggestion dotnet_diagnostic.CA1810.severity = suggestion
dotnet_diagnostic.CA1812.severity = suggestion dotnet_diagnostic.CA1812.severity = suggestion
dotnet_diagnostic.CA1813.severity = suggestion
dotnet_diagnostic.CA1814.severity = suggestion dotnet_diagnostic.CA1814.severity = suggestion
dotnet_diagnostic.CA1815.severity = suggestion dotnet_diagnostic.CA1815.severity = suggestion
dotnet_diagnostic.CA1816.severity = suggestion dotnet_diagnostic.CA1816.severity = suggestion
@@ -205,7 +202,6 @@ dotnet_diagnostic.CA2101.severity = suggestion
dotnet_diagnostic.CA2119.severity = suggestion dotnet_diagnostic.CA2119.severity = suggestion
dotnet_diagnostic.CA2153.severity = suggestion dotnet_diagnostic.CA2153.severity = suggestion
dotnet_diagnostic.CA2200.severity = suggestion dotnet_diagnostic.CA2200.severity = suggestion
dotnet_diagnostic.CA2201.severity = suggestion
dotnet_diagnostic.CA2207.severity = suggestion dotnet_diagnostic.CA2207.severity = suggestion
dotnet_diagnostic.CA2208.severity = suggestion dotnet_diagnostic.CA2208.severity = suggestion
dotnet_diagnostic.CA2211.severity = suggestion dotnet_diagnostic.CA2211.severity = suggestion
@@ -251,8 +247,6 @@ dotnet_diagnostic.CA5374.severity = suggestion
dotnet_diagnostic.CA5379.severity = suggestion dotnet_diagnostic.CA5379.severity = suggestion
dotnet_diagnostic.CA5384.severity = suggestion dotnet_diagnostic.CA5384.severity = suggestion
dotnet_diagnostic.CA5385.severity = suggestion dotnet_diagnostic.CA5385.severity = suggestion
dotnet_diagnostic.CA5392.severity = suggestion
dotnet_diagnostic.CA5394.severity = suggestion
dotnet_diagnostic.CA5397.severity = suggestion dotnet_diagnostic.CA5397.severity = suggestion
+2 -1
View File
@@ -1,6 +1,7 @@
{ {
"paths": [ "paths": [
"frontend/src/**/*.js" "frontend/src/**/*.js",
"src/NzbDrone.Core/Localization/Core/*.json"
], ],
"ignored": [ "ignored": [
"**/node_modules/**/*" "**/node_modules/**/*"
+1 -1
View File
@@ -1,6 +1,6 @@
# These are supported funding model platforms # These are supported funding model platforms
github: radarr github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username patreon: # Replace with a single Patreon username
open_collective: radarr open_collective: radarr
ko_fi: # Replace with a single Ko-fi username ko_fi: # Replace with a single Ko-fi username
+35
View File
@@ -0,0 +1,35 @@
---
name: Bug Report
about: 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!
title: ''
labels: bug
assignees: ''
---
**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.10]
- Docker: [Yes/No]
- Mono or.NET Core Version: [e.g. Mono 5.8 or .Net Core 3.1.10] (found under System -> Status)
- Browser and Version [e.g. chrome 86.0.4240.198] (Only needed for UI issues)
- Radarr Version [e.g. 3.0.0.2956]
- Radarr Branch [e.g. master]
**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!**
-75
View File
@@ -1,75 +0,0 @@
name: Bug Report
description: 'Report a new bug, if you are not 100% certain this is a bug please go to our Reddit or Discord first'
labels: ['Type: Bug', 'Status: Needs Triage']
body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an open or closed issue already exists for the bug you encountered. If a bug exists and is closed note that it may only be fixed in an unstable branch.
options:
- label: I have searched the existing open and closed 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)
- **Database**: Sqlite 3.36.0
value: |
- OS:
- Radarr:
- Docker Install:
- Using Reverse Proxy:
- Browser:
- Database:
render: markdown
validations:
required: true
- type: dropdown
attributes:
label: What branch are you running?
options:
- Master
- Develop
- Nightly
- Other (This issue will be closed)
validations:
required: true
- type: textarea
attributes:
label: Trace Logs?
description: |
Trace Logs (https://wiki.servarr.com/radarr/troubleshooting#logging-and-log-files)
***Generally speaking, all bug reports must have trace logs provided.***
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
Additionally, any additional info? Screenshots? References? Anything that will give us more context about the issue you are encountering!
validations:
required: true
+1 -1
View File
@@ -1,7 +1,7 @@
blank_issues_enabled: false blank_issues_enabled: false
contact_links: contact_links:
- name: Support via Discord - name: Support via Discord
url: https://radarr.video/discord url: https://discord.gg/r5wJPt9
about: Chat with users and devs on support and setup related topics. about: Chat with users and devs on support and setup related topics.
- name: Support via Reddit - name: Support via Reddit
url: https://reddit.com/r/radarr url: https://reddit.com/r/radarr
+20
View File
@@ -0,0 +1,20 @@
---
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.
@@ -1,38 +0,0 @@
name: Feature Request
description: 'Suggest an idea for Radarr'
labels: ['Type: Feature Request', 'Status: Needs Triage']
body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an open or closed issue already exists for the feature you are requesting. If a request exists and is closed note that it may only be fixed in an unstable branch.
options:
- label: I have searched the existing open and closed 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
+4 -5
View File
@@ -1,16 +1,15 @@
#### Database Migration #### Database Migration
YES - XXXX | NO YES | 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 (./src/NzbDrone.Core/Localization/Core/en.json) - [ ] Translation Keys
- [ ] [Wiki Updates](https://wiki.servarr.com) - [ ] Wiki Updates
#### Issues Fixed or Closed by this PR #### Issues Fixed or Closed by this PR
* Fixes #XXXX * Fixes #XXXX
+2
View File
@@ -0,0 +1,2 @@
todo:
keyword: "TODO"
+1
View File
@@ -0,0 +1 @@
+4 -3
View File
@@ -4,12 +4,13 @@ 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 #legacy - feature request
- 'Type: Feature Request' - parser
- 'Status: Confirmed' - confirmed
- sonarr-pull - sonarr-pull
- lidarr-pull - lidarr-pull
- readarr-pull - readarr-pull
- v3
# Label to use when marking an issue as stale # Label to use when marking an issue as stale
staleLabel: stale staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable # Comment to post when marking an issue as stale. Set to `false` to disable
+13
View File
@@ -0,0 +1,13 @@
# Configuration for support-requests - https://github.com/dessant/support-requests
# Label used to mark issues as support requests
supportLabel: 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://discord.gg/r5wJPt9) 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
-45
View File
@@ -1,45 +0,0 @@
name: Sync issue to Azure DevOps work item
on:
issues:
types:
[opened, edited, deleted, closed, reopened, labeled, unlabeled, assigned]
concurrency: azuresync-${{ github.event.issue.number }}
permissions: {}
jobs:
alert:
permissions:
issues: write # to update issue body
runs-on: ubuntu-latest
steps:
- uses: danhellem/github-actions-issue-to-work-item@master
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == true }}"
env:
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
github_token: "${{ github.token }}"
ado_organization: "Servarr"
ado_project: "Servarr"
ado_area_path: "Servarr\\Radarr"
ado_wit: "Bug"
ado_new_state: "New"
ado_active_state: "Active"
ado_close_state: "Closed"
ado_bypassrules: true
log_level: 100
- uses: danhellem/github-actions-issue-to-work-item@master
if: "${{ contains(github.event.issue.labels.*.name, 'Type: Bug') == false }}"
env:
ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}"
github_token: "${{ github.token }}"
ado_organization: "Servarr"
ado_project: "Servarr"
ado_area_path: "Servarr\\Radarr"
ado_wit: "User Story"
ado_new_state: "New"
ado_active_state: "Active"
ado_close_state: "Closed"
ado_bypassrules: true
log_level: 100
-5
View File
@@ -5,13 +5,8 @@ on:
schedule: schedule:
- cron: '0 0 * * *' - cron: '0 0 * * *'
permissions: {}
jobs: jobs:
lock: lock:
permissions:
issues: write # to lock issues (dessant/lock-threads)
pull-requests: write # to lock PRs (dessant/lock-threads)
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: dessant/lock-threads@v2 - uses: dessant/lock-threads@v2
-25
View File
@@ -1,25 +0,0 @@
name: 'Support requests'
on:
issues:
types: [labeled, unlabeled, reopened]
permissions: {}
jobs:
support:
permissions:
issues: write # to modify issues
runs-on: ubuntu-latest
steps:
- uses: dessant/support-requests@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
+20 -5
View File
@@ -166,12 +166,27 @@ packages.config.md5sum
# Common IntelliJ Platform excludes # Common IntelliJ Platform excludes
# Ignore Rider projects completely for now # User specific
.idea/ **/.idea/**/workspace.xml
**/.idea/**/tasks.xml
**/.idea/shelf/*
**/.idea/dictionaries
**/.idea/.idea.Radarr.Posix
**/.idea/.idea.Radarr.Windows
# Sensitive or high-churn files
**/.idea/**/dataSources/
**/.idea/**/dataSources.ids
**/.idea/**/dataSources.xml
**/.idea/**/dataSources.local.xml
**/.idea/**/sqlDataSources.xml
**/.idea/**/dynamic.xml
# Rider
# Rider auto-generates .iml files, and contentModel.xml
**/.idea/**/*.iml
**/.idea/**/contentModel.xml
**/.idea/**/modules.xml
# ignore node_modules symlink # ignore node_modules symlink
node_modules node_modules
node_modules.nosync node_modules.nosync
# API doc generation
.config/
-132
View File
@@ -1,132 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
<development@radarr.video>.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations
+42 -6
View File
@@ -1,13 +1,49 @@
# 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.
This file has been moved to the wiki for the latest details please see the [contributing wiki page](https://wiki.servarr.com/radarr/contributing). ## Documentation ##
Setup guides, FAQ, the more information we have on the [wiki](https://wiki.servarr.com/Radarr) the better.
## Documentation ## Development ##
Setup guides, [FAQ](https://wiki.servarr.com/radarr/faq), the more information we have on the [wiki](https://wiki.servarr.com/radarr) the better. ### Tools required ###
- Visual Studio 2019 or higher (https://www.visualstudio.com/vs/). The community version is free and works (https://www.visualstudio.com/downloads/).
- HTML/Javascript editor of choice (VS Code/Sublime Text/Webstorm/Atom/etc)
- [Git](https://git-scm.com/downloads)
- [NodeJS](https://nodejs.org/en/download/) (Node 10.X.X or higher)
- [Yarn](https://yarnpkg.com/)
- .NET Core 3.1.
## Development ### Getting started ###
See the [Wiki Page](https://wiki.servarr.com/radarr/contributing) 1. Fork Radarr
2. Clone the repository into your development machine. [*info*](https://help.github.com/articles/working-with-repositories)
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 `netcoreapp31`
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.
+23 -37
View File
@@ -1,21 +1,19 @@
# Radarr # Radarr
[![Build Status](https://dev.azure.com/Radarr/Radarr/_apis/build/status/Radarr.Radarr?branchName=develop)](https://dev.azure.com/Radarr/Radarr/_build/latest?definitionId=1&branchName=develop) [![Build Status](https://dev.azure.com/Radarr/Radarr/_apis/build/status/Radarr.Radarr?branchName=develop)](https://dev.azure.com/Radarr/Radarr/_build/latest?definitionId=1&branchName=develop)
[![Translated](https://translate.servarr.com/widgets/servarr/-/radarr/svg-badge.svg)](https://translate.servarr.com/engage/radarr/?utm_source=widget) [![Translated](https://translate.servarr.com/widgets/radarr/-/radarr/svg-badge.svg)](https://translate.servarr.com/engage/radarr/?utm_source=widget)
[![Docker Pulls](https://img.shields.io/docker/pulls/linuxserver/radarr.svg)](https://wiki.servarr.com/radarr/installation#docker) [![Docker Pulls](https://img.shields.io/docker/pulls/linuxserver/radarr.svg)](https://wiki.servarr.com/Radarr_Installation#Docker)
![Github Downloads](https://img.shields.io/github/downloads/Radarr/Radarr/total.svg) ![Github Downloads](https://img.shields.io/github/downloads/Radarr/Radarr/total.svg)
[![Backers on Open Collective](https://opencollective.com/Radarr/backers/badge.svg)](#backers) [![Backers on Open Collective](https://opencollective.com/Radarr/backers/badge.svg)](#backers)
[![Sponsors on Open Collective](https://opencollective.com/Radarr/sponsors/badge.svg)](#sponsors) [![Sponsors on Open Collective](https://opencollective.com/Radarr/sponsors/badge.svg)](#sponsors)
[![Mega Sponsors on Open Collective](https://opencollective.com/Radarr/megasponsors/badge.svg)](#mega-sponsors)
Radarr is a movie collection manager for Usenet and BitTorrent users. It can monitor multiple RSS feeds for new movies and will interface with clients and indexers to grab, sort, and rename them. It can also be configured to automatically upgrade the quality of existing files in the library when a better quality format becomes available. Radarr is a movie collection manager for Usenet and BitTorrent users. It can monitor multiple RSS feeds for new movies and will interface with clients and indexers to grab, sort, and rename them. It can also be configured to automatically upgrade the quality of existing files in the library when a better quality format becomes available.
Note that only one type of a given movie is supported. If you want both an 4k version and 1080p version of a given movie you will need multiple instances.
## Major Features Include ## Major Features Include:
* Adding new movies with lots of information, such as trailers, ratings, etc. * Adding new movies with lots of information, such as trailers, ratings, etc.
* Support for major platforms: Windows, Linux, macOS, Raspberry Pi, etc. * Support for major platforms: Windows, Linux, macOS, Raspberry Pi, etc.
* Can watch for better quality of the movies you have and do an automatic upgrade. *e.g. from DVD to Blu-Ray* * Can watch for better quality of the movies you have and do an automatic upgrade. *eg. from DVD to Blu-Ray*
* Automatic failed download handling will try another release if one fails * Automatic failed download handling will try another release if one fails
* Manual search so you can pick any release or to see why a release was not downloaded automatically * Manual search so you can pick any release or to see why a release was not downloaded automatically
* Full integration with SABnzbd and NZBGet * Full integration with SABnzbd and NZBGet
@@ -23,69 +21,57 @@ Note that only one type of a given movie is supported. If you want both an 4k ve
* Automatically importing downloaded movies * Automatically importing downloaded movies
* Recognizing Special Editions, Director's Cut, etc. * Recognizing Special Editions, Director's Cut, etc.
* Identifying releases with hardcoded subs * Identifying releases with hardcoded subs
* Identifying releases with AKA movie names * QBittorrent, Deluge, rTorrent, Transmission, uTorrent, and other download clients are supported
* SABnzbd, NZBGet, QBittorrent, Deluge, rTorrent, Transmission, uTorrent, and other download clients are supported and integrated * Full integration with Kodi, Plex (notification, library update)
* Full integration with Kodi and Plex (notifications, library updates) * A beautiful UI
* Importing Metadata such as trailers or subtitles * Importing Metadata such as trailers or subtitles
* Adding metadata such as posters and information for Kodi and others to use * Adding metadata such as posters and information for Kodi and others to use
* Advanced customization for profiles, such that Radarr will always download the copy you want * Advanced customization for profiles, such that Radarr will always download the copy you want
* A beautiful UI
## Support ## Support
[![Wiki](https://img.shields.io/badge/servarr-wiki-181717.svg?maxAge=60)](https://wiki.servarr.com/radarr)
[![Discord](https://img.shields.io/badge/discord-chat-7289DA.svg?maxAge=60)](https://radarr.video/discord)
[![Reddit](https://img.shields.io/badge/reddit-discussion-FF4500.svg?maxAge=60)](https://www.reddit.com/r/Radarr)
Note: GitHub Issues are for Bugs and Feature Requests Only Note: GitHub Issues are for Bugs and Feature Requests Only
[![Discord](https://img.shields.io/badge/discord-chat-7289DA.svg?maxAge=60)](https://discord.gg/r5wJPt9)
[![Reddit](https://img.shields.io/badge/reddit-discussion-FF4500.svg?maxAge=60)](https://www.reddit.com/r/Radarr)
[![GitHub - Bugs and Feature Requests Only](https://img.shields.io/badge/github-issues-red.svg?maxAge=60)](https://github.com/Radarr/Radarr/issues) [![GitHub - Bugs and Feature Requests Only](https://img.shields.io/badge/github-issues-red.svg?maxAge=60)](https://github.com/Radarr/Radarr/issues)
[![Wiki](https://img.shields.io/badge/servarr-wiki-181717.svg?maxAge=60)](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/)
This project exists thanks to all the people who contribute. This project exists thanks to all the people who contribute. [Contribute](CONTRIBUTING.md).
- [Contribute (GitHub)](CONTRIBUTING.md) <a href="https://github.com/Radarr/Radarr/graphs/contributors"><img src="https://opencollective.com/Radarr/contributors.svg?width=890&button=false" /></a>
- [Contribution (Wiki Article)](https://wiki.servarr.com/radarr/contributing)
[![Contributors List](https://opencollective.com/Radarr/contributors.svg?width=890&button=false)](https://github.com/Radarr/Radarr/graphs/contributors)
## Backers ## Backers
Thank you to all our backers! 🙏 [Become a backer](https://opencollective.com/Radarr#backer) Thank you to all our backers! 🙏 [Become a backer](https://opencollective.com/Radarr#backer)
[![Backers List](https://opencollective.com/Radarr/backers.svg?width=890)](https://opencollective.com/Radarr#backer) <img src="https://opencollective.com/Radarr/backers.svg?width=890"></a>
## Sponsors ## Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor](https://opencollective.com/Radarr#sponsor) Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor](https://opencollective.com/Radarr#sponsor)
[![Sponsors List](https://opencollective.com/Radarr/sponsors.svg?width=890)](https://opencollective.com/Radarr#sponsor) <img src="https://opencollective.com/Radarr/sponsors.svg?width=890"></a>
## Mega Sponsors ## Mega Sponsors
[![Mega Sponsors List](https://opencollective.com/Radarr/tiers/mega-sponsor.svg?width=890)](https://opencollective.com/Radarr#mega-sponsor) <img src="https://opencollective.com/Radarr/tiers/mega-sponsor.svg?width=890"></a>
## JetBrains ## JetBrains
Thank you to [<img src="/Logo/jetbrains.svg" alt="JetBrains" width="32"> JetBrains](http://www.jetbrains.com/) for providing us with free licenses to their great tools. Thank you to [<img src="/Logo/jetbrains.svg" alt="JetBrains" width="32"> JetBrains](http://www.jetbrains.com/) for providing us with free licenses to their great tools.
* [<img src="/Logo/resharper.svg" alt="ReSharper" width="32"> ReSharper](http://www.jetbrains.com/resharper/) * [<img src="/Logo/resharper.svg" alt="ReSharper" width="32"> ReSharper](http://www.jetbrains.com/resharper/)
* [<img src="/Logo/webstorm.svg" alt="WebStorm" width="32"> WebStorm](http://www.jetbrains.com/webstorm/) * [<img src="/Logo/webstorm.svg" alt="WebStorm" width="32"> WebStorm](http://www.jetbrains.com/webstorm/)
* [<img src="/Logo/rider.svg" alt="Rider" width="32"> Rider](http://www.jetbrains.com/rider/) * [<img src="/Logo/rider.svg" alt="Rider" width="32"> Rider](http://www.jetbrains.com/rider/)
* [<img src="/Logo/dottrace.svg" alt="dotTrace" width="32"> dotTrace](http://www.jetbrains.com/dottrace/) * [<img src="/Logo/dottrace.svg" alt="dotTrace" width="32"> dotTrace](http://www.jetbrains.com/dottrace/)
## DigitalOcean
This project is also supported by DigitalOcean
<p>
<a href="https://www.digitalocean.com/">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="201px">
</a>
</p>
### License ### License
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html) * [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
* Copyright 2010-2022 * Copyright 2010-2021
-8
View File
@@ -1,8 +0,0 @@
# Security Policy
## Reporting a Vulnerability
Please report (suspected) security vulnerabilities on Discord (preferred) to
any of the Servarr Dev role holders (red names) or via email: development@servarr.com. You will receive a response from
us within 72 hours. If the issue is confirmed, we will release a patch as soon
as possible depending on complexity/severity.
+161 -427
View File
@@ -7,20 +7,14 @@ variables:
outputFolder: './_output' outputFolder: './_output'
artifactsFolder: './_artifacts' artifactsFolder: './_artifacts'
testsFolder: './_tests' testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn majorVersion: '3.0.2'
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '4.4.1'
minorVersion: $[counter('minorVersion', 2000)] minorVersion: $[counter('minorVersion', 2000)]
radarrVersion: '$(majorVersion).$(minorVersion)' radarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(radarrVersion)' buildName: '$(Build.SourceBranchName).$(radarrVersion)'
sentryOrg: 'servarr' sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com' sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '6.0.400' dotnetVersion: '3.1.404'
nodeVersion: '16.X' yarnCacheFolder: $(Pipeline.Workspace)/.yarn
innoVersion: '6.2.0'
windowsImage: 'windows-2022'
linuxImage: 'ubuntu-20.04'
macImage: 'macOS-11'
trigger: trigger:
branches: branches:
@@ -29,13 +23,7 @@ trigger:
- master - master
pr: pr:
branches: - develop
include:
- develop
paths:
exclude:
- src/NzbDrone.Core/Localization/Core
- src/Radarr.Api.*/openapi.json
stages: stages:
- stage: Setup - stage: Setup
@@ -44,7 +32,7 @@ stages:
- job: - job:
displayName: Build Variables displayName: Build Variables
pool: pool:
vmImage: ${{ variables.linuxImage }} vmImage: 'ubuntu-18.04'
steps: steps:
# Set the build name properly. The 'name' property won't recursively expand so hack here: # Set the build name properly. The 'name' property won't recursively expand so hack here:
- bash: echo "##vso[build.updatebuildnumber]$RADARRVERSION" - bash: echo "##vso[build.updatebuildnumber]$RADARRVERSION"
@@ -70,22 +58,19 @@ stages:
matrix: matrix:
Linux: Linux:
osName: 'Linux' osName: 'Linux'
imageName: ${{ variables.linuxImage }} imageName: 'ubuntu-18.04'
enableAnalysis: 'true'
Mac: Mac:
osName: 'Mac' osName: 'Mac'
imageName: ${{ variables.macImage }} imageName: 'macos-10.14'
enableAnalysis: 'false'
Windows: Windows:
osName: 'Windows' osName: 'Windows'
imageName: ${{ variables.windowsImage }} imageName: 'windows-2019'
enableAnalysis: 'false'
pool: pool:
vmImage: $(imageName) vmImage: $(imageName)
variables: variables:
# Disable stylecop here - linting errors get caught by the analyze task # Disable stylecop here - linting errors get caught by the analyze task
EnableAnalyzers: $(enableAnalysis) EnableAnalyzers: 'false'
steps: steps:
- checkout: self - checkout: self
submodules: true submodules: true
@@ -94,17 +79,7 @@ stages:
displayName: 'Install .net core' displayName: 'Install .net core'
inputs: inputs:
version: $(dotnetVersion) version: $(dotnetVersion)
- bash: | - bash: ./build.sh --backend
BUNDLEDVERSIONS=${AGENT_TOOLSDIRECTORY}/dotnet/sdk/${DOTNETVERSION}/Microsoft.NETCoreSdk.BundledVersions.props
echo $BUNDLEDVERSIONS
if grep -q freebsd-x64 $BUNDLEDVERSIONS; then
echo "Extra platforms already enabled"
else
echo "Enabling extra platform support"
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
fi
displayName: Enable Extra Platform Support
- bash: ./build.sh --backend --enable-extra-platforms
displayName: Build Radarr Backend displayName: Build Radarr Backend
- bash: | - bash: |
find ${OUTPUTFOLDER} -type f ! -path "*/publish/*" -exec rm -rf {} \; find ${OUTPUTFOLDER} -type f ! -path "*/publish/*" -exec rm -rf {} \;
@@ -117,29 +92,25 @@ stages:
artifact: '$(osName)Backend' artifact: '$(osName)Backend'
displayName: Publish Backend displayName: Publish Backend
condition: and(succeeded(), eq(variables['osName'], 'Windows')) condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/win-x64/publish' - publish: '$(testsFolder)/netcoreapp3.1/win-x64/publish'
artifact: win-x64-tests artifact: WindowsCoreTests
displayName: Publish win-x64 Test Package displayName: Publish Windows Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows')) condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/linux-x64/publish' - publish: '$(testsFolder)/net462/linux-x64/publish'
artifact: linux-x64-tests artifact: LinuxTests
displayName: Publish linux-x64 Test Package displayName: Publish Linux Mono Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows')) condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/linux-x86/publish' - publish: '$(testsFolder)/netcoreapp3.1/linux-x64/publish'
artifact: linux-x86-tests artifact: LinuxCoreTests
displayName: Publish linux-x86 Test Package displayName: Publish Linux Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows')) condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/linux-musl-x64/publish' - publish: '$(testsFolder)/netcoreapp3.1/linux-musl-x64/publish'
artifact: linux-musl-x64-tests artifact: LinuxMuslCoreTests
displayName: Publish linux-musl-x64 Test Package displayName: Publish Linux Musl Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows')) condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/freebsd-x64/publish' - publish: '$(testsFolder)/netcoreapp3.1/osx-x64/publish'
artifact: freebsd-x64-tests artifact: MacCoreTests
displayName: Publish freebsd-x64 Test Package displayName: Publish MacOS Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- publish: '$(testsFolder)/net6.0/osx-x64/publish'
artifact: osx-x64-tests
displayName: Publish osx-x64 Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows')) condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- stage: Build_Frontend - stage: Build_Frontend
@@ -151,20 +122,20 @@ stages:
matrix: matrix:
Linux: Linux:
osName: 'Linux' osName: 'Linux'
imageName: ${{ variables.linuxImage }} imageName: 'ubuntu-18.04'
Mac: Mac:
osName: 'Mac' osName: 'Mac'
imageName: ${{ variables.macImage }} imageName: 'macos-10.14'
Windows: Windows:
osName: 'Windows' osName: 'Windows'
imageName: ${{ variables.windowsImage }} imageName: 'windows-2019'
pool: pool:
vmImage: $(imageName) vmImage: $(imageName)
steps: steps:
- task: NodeTool@0 - task: NodeTool@0
displayName: Set Node.js version displayName: Set Node.js version
inputs: inputs:
versionSpec: $(nodeVersion) versionSpec: '10.x'
- checkout: self - checkout: self
submodules: true submodules: true
fetchDepth: 1 fetchDepth: 1
@@ -173,6 +144,7 @@ stages:
key: 'yarn | "$(osName)" | yarn.lock' key: 'yarn | "$(osName)" | yarn.lock'
restoreKeys: | restoreKeys: |
yarn | "$(osName)" yarn | "$(osName)"
yarn
path: $(yarnCacheFolder) path: $(yarnCacheFolder)
displayName: Cache Yarn packages displayName: Cache Yarn packages
- bash: ./build.sh --frontend - bash: ./build.sh --frontend
@@ -193,7 +165,7 @@ stages:
- job: Windows_Installer - job: Windows_Installer
displayName: Create Installer displayName: Create Installer
pool: pool:
vmImage: ${{ variables.windowsImage }} vmImage: 'windows-2019'
steps: steps:
- checkout: self - checkout: self
fetchDepth: 1 fetchDepth: 1
@@ -209,11 +181,16 @@ stages:
artifactName: WindowsFrontend artifactName: WindowsFrontend
targetPath: _output targetPath: _output
displayName: Fetch Frontend displayName: Fetch Frontend
- bash: ./build.sh --packages
displayName: Create Packages
- bash: | - bash: |
./build.sh --packages --installer setup/inno/ISCC.exe setup/radarr.iss //DFramework=netcoreapp3.1 //DRuntime=win-x86
cp setup/output/Radarr.*win-x64.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x64-installer.exe cp setup/output/Radarr.*windows.netcoreapp3.1.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x86-installer.exe
cp setup/output/Radarr.*win-x86.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x86-installer.exe displayName: Create .NET Core Windows installer
displayName: Create Installers - bash: |
setup/inno/ISCC.exe setup/radarr.iss //DFramework=netcoreapp3.1 //DRuntime=win-x64
cp setup/output/Radarr.*windows.netcoreapp3.1.exe ${BUILD_ARTIFACTSTAGINGDIRECTORY}/Radarr.${BUILDNAME}.windows-core-x64-installer.exe
displayName: Create .NET Core Windows installer
- publish: $(Build.ArtifactStagingDirectory) - publish: $(Build.ArtifactStagingDirectory)
artifact: 'WindowsInstaller' artifact: 'WindowsInstaller'
displayName: Publish Installer displayName: Publish Installer
@@ -226,7 +203,7 @@ stages:
- job: Other_Packages - job: Other_Packages
displayName: Create Standard Packages displayName: Create Standard Packages
pool: pool:
vmImage: ${{ variables.linuxImage }} vmImage: 'ubuntu-18.04'
steps: steps:
- checkout: self - checkout: self
fetchDepth: 1 fetchDepth: 1
@@ -242,121 +219,89 @@ stages:
artifactName: WindowsFrontend artifactName: WindowsFrontend
targetPath: _output targetPath: _output
displayName: Fetch Frontend displayName: Fetch Frontend
- bash: ./build.sh --packages --enable-extra-platforms - bash: ./build.sh --packages
displayName: Create Packages displayName: Create Packages
- bash: | - bash: |
find . -name "ffprobe" -exec chmod a+x {} \;
find . -name "Radarr" -exec chmod a+x {} \; find . -name "Radarr" -exec chmod a+x {} \;
find . -name "Radarr.Update" -exec chmod a+x {} \; find . -name "Radarr.Update" -exec chmod a+x {} \;
displayName: Set executable bits displayName: Set executable bits
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create win-x64 zip displayName: Create Windows Core zip
inputs: inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x64.zip' archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x64.zip'
archiveType: 'zip' archiveType: 'zip'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/win-x64/net6.0 rootFolderOrFile: $(artifactsFolder)/win-x64/netcoreapp3.1
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create win-x86 zip displayName: Create Windows x86 Core zip
inputs: inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x86.zip' archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).windows-core-x86.zip'
archiveType: 'zip' archiveType: 'zip'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/win-x86/net6.0 rootFolderOrFile: $(artifactsFolder)/win-x86/netcoreapp3.1
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create osx-x64 app displayName: Create MacOS Core app
inputs: inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-app-core-x64.zip' archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-app-core-x64.zip'
archiveType: 'zip' archiveType: 'zip'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/osx-x64-app/net6.0 rootFolderOrFile: $(artifactsFolder)/macos-app/netcoreapp3.1
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create osx-x64 tar displayName: Create MacOS Core tar
inputs: inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-core-x64.tar.gz' archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-core-x64.tar.gz'
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/osx-x64/net6.0 rootFolderOrFile: $(artifactsFolder)/macos/netcoreapp3.1
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create osx-arm64 app displayName: Create Linux Mono tar
inputs: inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-app-core-arm64.zip' archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux.tar.gz'
archiveType: 'zip'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/osx-arm64-app/net6.0
- task: ArchiveFiles@2
displayName: Create osx-arm64 tar
inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).osx-core-arm64.tar.gz'
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/osx-arm64/net6.0 rootFolderOrFile: $(artifactsFolder)/linux-x64/net462
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create linux-x64 tar displayName: Create Linux Core tar
inputs: inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-core-x64.tar.gz' archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-core-x64.tar.gz'
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-x64/net6.0 rootFolderOrFile: $(artifactsFolder)/linux-x64/netcoreapp3.1
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create linux-musl-x64 tar displayName: Create Linux Musl Core tar
inputs: inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-musl-core-x64.tar.gz' archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-musl-core-x64.tar.gz'
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net6.0 rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/netcoreapp3.1
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create linux-x86 tar displayName: Create ARM32 Linux Core tar
inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-core-x86.tar.gz'
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-x86/net6.0
- task: ArchiveFiles@2
displayName: Create linux-arm tar
inputs: inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-core-arm.tar.gz' archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-core-arm.tar.gz'
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-arm/net6.0 rootFolderOrFile: $(artifactsFolder)/linux-arm/netcoreapp3.1
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create linux-musl-arm tar displayName: Create ARM64 Linux Core tar
inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-musl-core-arm.tar.gz'
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm/net6.0
- task: ArchiveFiles@2
displayName: Create linux-arm64 tar
inputs: inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-core-arm64.tar.gz' archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-core-arm64.tar.gz'
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-arm64/net6.0 rootFolderOrFile: $(artifactsFolder)/linux-arm64/netcoreapp3.1
- task: ArchiveFiles@2 - task: ArchiveFiles@2
displayName: Create linux-musl-arm64 tar displayName: Create ARM64 Linux Musl Core tar
inputs: inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-musl-core-arm64.tar.gz' archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).linux-musl-core-arm64.tar.gz'
archiveType: 'tar' archiveType: 'tar'
tarCompression: 'gz' tarCompression: 'gz'
includeRootFolder: false includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/net6.0 rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/netcoreapp3.1
- task: ArchiveFiles@2
displayName: Create freebsd-x64 tar
inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Radarr.$(buildName).freebsd-core-x64.tar.gz'
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
rootFolderOrFile: $(artifactsFolder)/freebsd-x64/net6.0
- publish: $(Build.ArtifactStagingDirectory) - publish: $(Build.ArtifactStagingDirectory)
artifact: 'Packages' artifact: 'Packages'
displayName: Publish Packages displayName: Publish Packages
@@ -395,7 +340,7 @@ stages:
jobs: jobs:
- job: Prepare - job: Prepare
pool: pool:
vmImage: ${{ variables.linuxImage }} vmImage: 'ubuntu-18.04'
steps: steps:
- checkout: none - checkout: none
- task: DownloadPipelineArtifact@2 - task: DownloadPipelineArtifact@2
@@ -410,34 +355,24 @@ stages:
displayName: Unit Native displayName: Unit Native
dependsOn: Prepare dependsOn: Prepare
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0')) condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
workspace:
clean: all
strategy: strategy:
matrix: matrix:
MacCore: MacCore:
osName: 'Mac' osName: 'Mac'
testName: 'osx-x64' testName: 'MacCore'
poolName: 'Azure Pipelines' imageName: 'macos-10.14'
imageName: ${{ variables.macImage }}
WindowsCore: WindowsCore:
osName: 'Windows' osName: 'Windows'
testName: 'win-x64' testName: 'WindowsCore'
poolName: 'Azure Pipelines' imageName: 'windows-2019'
imageName: ${{ variables.windowsImage }}
LinuxCore: LinuxCore:
osName: 'Linux' osName: 'Linux'
testName: 'linux-x64' testName: 'LinuxCore'
poolName: 'Azure Pipelines' imageName: 'ubuntu-18.04'
imageName: ${{ variables.linuxImage }} pattern: 'Radarr.**.linux-core-x64.tar.gz'
FreebsdCore:
osName: 'Linux'
testName: 'freebsd-x64'
poolName: 'FreeBSD'
imageName:
pool: pool:
name: $(poolName)
vmImage: $(imageName) vmImage: $(imageName)
steps: steps:
@@ -446,20 +381,30 @@ stages:
displayName: 'Install .net core' displayName: 'Install .net core'
inputs: inputs:
version: $(dotnetVersion) version: $(dotnetVersion)
condition: ne(variables['poolName'], 'FreeBSD')
- task: DownloadPipelineArtifact@2 - task: DownloadPipelineArtifact@2
displayName: Download Test Artifact displayName: Download Test Artifact
inputs: inputs:
buildType: 'current' buildType: 'current'
artifactName: '$(testName)-tests' artifactName: '$(testName)Tests'
targetPath: $(testsFolder) targetPath: $(testsFolder)
- bash: |
wget https://mediaarea.net/repo/deb/repo-mediaarea_1.0-11_all.deb
sudo dpkg -i repo-mediaarea_1.0-11_all.deb
sudo apt-get update
sudo apt-get install -y --allow-unauthenticated libmediainfo-dev libmediainfo0v5 mediainfo
displayName: Install mediainfo
condition: and(succeeded(), eq(variables['osName'], 'Linux'))
- 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: | - bash: |
chmod a+x _tests/ffprobe SYMLINK=6_6_0
displayName: Make ffprobe Executable MONOPREFIX=/Library/Frameworks/Mono.framework/Versions/$SYMLINK
condition: and(succeeded(), ne(variables['osName'], 'Windows')) 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'))
@@ -483,76 +428,30 @@ 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: servarr/testimages:mono-5.20
mono610:
testName: 'Mono 6.10'
artifactName: LinuxTests
containerImage: servarr/testimages:mono-6.10
mono612:
testName: 'Mono 6.12'
artifactName: LinuxTests
containerImage: servarr/testimages:mono-6.12
alpine: alpine:
testName: 'Musl Net Core' testName: 'Musl Net Core'
artifactName: linux-musl-x64-tests artifactName: LinuxMuslCoreTests
containerImage: ghcr.io/servarr/testimages:alpine containerImage: servarr/testimages:alpine
linux-x86:
testName: 'linux-x86'
artifactName: linux-x86-tests
containerImage: ghcr.io/servarr/testimages:linux-x86
pool: pool:
vmImage: ${{ variables.linuxImage }} vmImage: 'ubuntu-18.04'
container: $[ variables['containerImage'] ] container: $[ variables['containerImage'] ]
timeoutInMinutes: 10 timeoutInMinutes: 10
steps:
- task: UseDotNet@2
displayName: 'Install .NET'
inputs:
version: $(dotnetVersion)
condition: and(succeeded(), ne(variables['testName'], 'linux-x86'))
- bash: |
SDKURL=$(curl -s https://api.github.com/repos/Servarr/dotnet-linux-x86/releases | jq -rc '.[].assets[].browser_download_url' | grep sdk-${DOTNETVERSION}.*gz$)
curl -fsSL $SDKURL | tar xzf - -C /opt/dotnet
displayName: 'Install .NET'
condition: and(succeeded(), eq(variables['testName'], 'linux-x86'))
- checkout: none
- task: DownloadPipelineArtifact@2
displayName: Download Test Artifact
inputs:
buildType: 'current'
artifactName: $(artifactName)
targetPath: $(testsFolder)
- bash: |
chmod a+x _tests/ffprobe
displayName: Make ffprobe Executable
- bash: find ${TESTSFOLDER} -name "Radarr.Test.Dummy" -exec chmod a+x {} \;
displayName: Make Test Dummy Executable
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
- bash: |
chmod a+x ${TESTSFOLDER}/test.sh
ls -lR ${TESTSFOLDER}
${TESTSFOLDER}/test.sh Linux Unit Test
displayName: Run Tests
- task: PublishTestResults@2
displayName: Publish Test Results
inputs:
testResultsFormat: 'NUnit'
testResultsFiles: '**/TestResult.xml'
testRunTitle: '$(testName) Unit Tests'
failTaskOnFailedTests: true
- job: Unit_LinuxCore_Postgres
displayName: Unit Native LinuxCore with Postgres Database
dependsOn: Prepare
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
variables:
pattern: 'Radarr.*.linux-core-x64.tar.gz'
artifactName: linux-x64-tests
Radarr__Postgres__Host: 'localhost'
Radarr__Postgres__Port: '5432'
Radarr__Postgres__User: 'radarr'
Radarr__Postgres__Password: 'radarr'
pool:
vmImage: ${{ variables.linuxImage }}
timeoutInMinutes: 10
steps: steps:
- task: UseDotNet@2 - task: UseDotNet@2
displayName: 'Install .net core' displayName: 'Install .net core'
@@ -565,20 +464,9 @@ stages:
buildType: 'current' buildType: 'current'
artifactName: $(artifactName) artifactName: $(artifactName)
targetPath: $(testsFolder) targetPath: $(testsFolder)
- bash: |
chmod a+x _tests/ffprobe
displayName: Make ffprobe Executable
- 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'))
- bash: |
docker run -d --name=postgres14 \
-e POSTGRES_PASSWORD=radarr \
-e POSTGRES_USER=radarr \
-p 5432:5432/tcp \
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
postgres:14
displayName: Start postgres
- bash: | - bash: |
chmod a+x ${TESTSFOLDER}/test.sh chmod a+x ${TESTSFOLDER}/test.sh
ls -lR ${TESTSFOLDER} ls -lR ${TESTSFOLDER}
@@ -589,7 +477,7 @@ stages:
inputs: inputs:
testResultsFormat: 'NUnit' testResultsFormat: 'NUnit'
testResultsFiles: '**/TestResult.xml' testResultsFiles: '**/TestResult.xml'
testRunTitle: 'LinuxCore Postgres Unit Tests' testRunTitle: '$(testName) Unit Tests'
failTaskOnFailedTests: true failTaskOnFailedTests: true
- stage: Integration - stage: Integration
@@ -599,7 +487,7 @@ stages:
jobs: jobs:
- job: Prepare - job: Prepare
pool: pool:
vmImage: ${{ variables.linuxImage }} vmImage: 'ubuntu-18.04'
steps: steps:
- checkout: none - checkout: none
- task: DownloadPipelineArtifact@2 - task: DownloadPipelineArtifact@2
@@ -618,24 +506,32 @@ stages:
matrix: matrix:
MacCore: MacCore:
osName: 'Mac' osName: 'Mac'
testName: 'osx-x64' testName: 'MacCore'
imageName: ${{ variables.macImage }} imageName: 'macos-10.14'
pattern: 'Radarr.*.osx-core-x64.tar.gz' pattern: 'Radarr.**.osx-core-x64.tar.gz'
WindowsCore: WindowsCore:
osName: 'Windows' osName: 'Windows'
testName: 'win-x64' testName: 'WindowsCore'
imageName: ${{ variables.windowsImage }} imageName: 'windows-2019'
pattern: 'Radarr.*.windows-core-x64.zip' pattern: 'Radarr.**.windows-core-x64.zip'
LinuxCore: LinuxCore:
osName: 'Linux' osName: 'Linux'
testName: 'linux-x64' testName: 'LinuxCore'
imageName: ${{ variables.linuxImage }} imageName: 'ubuntu-18.04'
pattern: 'Radarr.*.linux-core-x64.tar.gz' pattern: 'Radarr.**.linux-core-x64.tar.gz'
pool: pool:
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:
@@ -645,7 +541,7 @@ stages:
displayName: Download Test Artifact displayName: Download Test Artifact
inputs: inputs:
buildType: 'current' buildType: 'current'
artifactName: '$(testName)-tests' artifactName: '$(testName)Tests'
targetPath: $(testsFolder) targetPath: $(testsFolder)
- task: DownloadPipelineArtifact@2 - task: DownloadPipelineArtifact@2
displayName: Download Build Artifact displayName: Download Build Artifact
@@ -675,132 +571,34 @@ stages:
failTaskOnFailedTests: true failTaskOnFailedTests: true
displayName: Publish Test Results displayName: Publish Test Results
- job: Integration_LinuxCore_Postgres
displayName: Integration Native LinuxCore with Postgres Database
dependsOn: Prepare
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
variables:
pattern: 'Radarr.*.linux-core-x64.tar.gz'
Radarr__Postgres__Host: 'localhost'
Radarr__Postgres__Port: '5432'
Radarr__Postgres__User: 'radarr'
Radarr__Postgres__Password: 'radarr'
pool:
vmImage: ${{ variables.linuxImage }}
steps:
- task: UseDotNet@2
displayName: 'Install .net core'
inputs:
version: $(dotnetVersion)
- checkout: none
- task: DownloadPipelineArtifact@2
displayName: Download Test Artifact
inputs:
buildType: 'current'
artifactName: 'linux-x64-tests'
targetPath: $(testsFolder)
- task: DownloadPipelineArtifact@2
displayName: Download Build Artifact
inputs:
buildType: 'current'
artifactName: Packages
itemPattern: '**/$(pattern)'
targetPath: $(Build.ArtifactStagingDirectory)
- task: ExtractFiles@1
inputs:
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
displayName: Extract Package
- bash: |
mkdir -p ./bin/
cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Radarr/. ./bin/
displayName: Move Package Contents
- bash: |
docker run -d --name=postgres14 \
-e POSTGRES_PASSWORD=radarr \
-e POSTGRES_USER=radarr \
-p 5432:5432/tcp \
-v /usr/share/zoneinfo/America/Chicago:/etc/localtime:ro \
postgres:14
displayName: Start postgres
- bash: |
chmod a+x ${TESTSFOLDER}/test.sh
${TESTSFOLDER}/test.sh Linux Integration Test
displayName: Run Integration Tests
- task: PublishTestResults@2
inputs:
testResultsFormat: 'NUnit'
testResultsFiles: '**/TestResult.xml'
testRunTitle: 'Integration LinuxCore Postgres Database Integration Tests'
failTaskOnFailedTests: true
displayName: Publish Test Results
- job: Integration_FreeBSD
displayName: Integration Native FreeBSD
dependsOn: Prepare
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
workspace:
clean: all
variables:
pattern: 'Radarr.*.freebsd-core-x64.tar.gz'
pool:
name: 'FreeBSD'
steps:
- checkout: none
- task: DownloadPipelineArtifact@2
displayName: Download Test Artifact
inputs:
buildType: 'current'
artifactName: 'freebsd-x64-tests'
targetPath: $(testsFolder)
- task: DownloadPipelineArtifact@2
displayName: Download Build Artifact
inputs:
buildType: 'current'
artifactName: Packages
itemPattern: '**/$(pattern)'
targetPath: $(Build.ArtifactStagingDirectory)
- bash: |
mkdir -p ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin
tar xf ${BUILD_ARTIFACTSTAGINGDIRECTORY}/$(pattern) -C ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin
displayName: Extract Package
- bash: |
mkdir -p ./bin/
cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Radarr/. ./bin/
displayName: Move Package Contents
- bash: |
chmod a+x ${TESTSFOLDER}/test.sh
${TESTSFOLDER}/test.sh Linux Integration Test
displayName: Run Integration Tests
- task: PublishTestResults@2
inputs:
testResultsFormat: 'NUnit'
testResultsFiles: '**/TestResult.xml'
testRunTitle: 'FreeBSD Integration Tests'
failTaskOnFailedTests: true
displayName: Publish Test Results
- job: Integration_Docker - job: Integration_Docker
displayName: Integration Docker displayName: Integration Docker
dependsOn: Prepare dependsOn: Prepare
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: servarr/testimages:mono-5.20
pattern: 'Radarr.**.linux.tar.gz'
mono610:
testName: 'Mono 6.10'
artifactName: LinuxTests
containerImage: servarr/testimages:mono-6.10
pattern: 'Radarr.**.linux.tar.gz'
mono612:
testName: 'Mono 6.12'
artifactName: LinuxTests
containerImage: servarr/testimages:mono-6.12
pattern: 'Radarr.**.linux.tar.gz'
alpine: alpine:
testName: 'linux-musl-x64' testName: 'Musl Net Core'
artifactName: linux-musl-x64-tests artifactName: LinuxMuslCoreTests
containerImage: ghcr.io/servarr/testimages:alpine containerImage: servarr/testimages:alpine
pattern: 'Radarr.*.linux-musl-core-x64.tar.gz' pattern: 'Radarr.**.linux-musl-core-x64.tar.gz'
linux-x86:
testName: 'linux-x86'
artifactName: linux-x86-tests
containerImage: ghcr.io/servarr/testimages:linux-x86
pattern: 'Radarr.*.linux-core-x86.tar.gz'
pool: pool:
vmImage: ${{ variables.linuxImage }} vmImage: 'ubuntu-18.04'
container: $[ variables['containerImage'] ] container: $[ variables['containerImage'] ]
@@ -808,15 +606,9 @@ stages:
steps: steps:
- task: UseDotNet@2 - task: UseDotNet@2
displayName: 'Install .NET' displayName: 'Install .net core'
inputs: inputs:
version: $(dotnetVersion) version: $(dotnetVersion)
condition: and(succeeded(), ne(variables['testName'], 'linux-x86'))
- bash: |
SDKURL=$(curl -s https://api.github.com/repos/Servarr/dotnet-linux-x86/releases | jq -rc '.[].assets[].browser_download_url' | grep sdk-${DOTNETVERSION}.*gz$)
curl -fsSL $SDKURL | tar xzf - -C /opt/dotnet
displayName: 'Install .NET'
condition: and(succeeded(), eq(variables['testName'], 'linux-x86'))
- checkout: none - checkout: none
- task: DownloadPipelineArtifact@2 - task: DownloadPipelineArtifact@2
displayName: Download Test Artifact displayName: Download Test Artifact
@@ -862,21 +654,18 @@ stages:
matrix: matrix:
Linux: Linux:
osName: 'Linux' osName: 'Linux'
artifactName: 'linux-x64' imageName: 'ubuntu-18.04'
imageName: ${{ variables.linuxImage }} pattern: 'Radarr.**.linux-core-x64.tar.gz'
pattern: 'Radarr.*.linux-core-x64.tar.gz'
failBuild: true failBuild: true
Mac: Mac:
osName: 'Mac' osName: 'Mac'
artifactName: 'osx-x64' imageName: 'macos-10.14'
imageName: ${{ variables.macImage }} pattern: 'Radarr.**.osx-core-x64.tar.gz'
pattern: 'Radarr.*.osx-core-x64.tar.gz'
failBuild: true failBuild: true
Windows: Windows:
osName: 'Windows' osName: 'Windows'
artifactName: 'win-x64' imageName: 'windows-2019'
imageName: ${{ variables.windowsImage }} pattern: 'Radarr.**.windows-core-x64.zip'
pattern: 'Radarr.*.windows-core-x64.zip'
failBuild: true failBuild: true
pool: pool:
@@ -892,7 +681,7 @@ stages:
displayName: Download Test Artifact displayName: Download Test Artifact
inputs: inputs:
buildType: 'current' buildType: 'current'
artifactName: '$(artifactName)-tests' artifactName: '$(osName)CoreTests'
targetPath: $(testsFolder) targetPath: $(testsFolder)
- task: DownloadPipelineArtifact@2 - task: DownloadPipelineArtifact@2
displayName: Download Build Artifact displayName: Download Build Artifact
@@ -941,7 +730,7 @@ stages:
jobs: jobs:
- job: Prepare - job: Prepare
pool: pool:
vmImage: ${{ variables.linuxImage }} vmImage: 'ubuntu-18.04'
steps: steps:
- checkout: none - checkout: none
- task: DownloadPipelineArtifact@2 - task: DownloadPipelineArtifact@2
@@ -958,17 +747,17 @@ stages:
matrix: matrix:
Linux: Linux:
osName: 'Linux' osName: 'Linux'
imageName: ${{ variables.linuxImage }} imageName: 'ubuntu-18.04'
Windows: Windows:
osName: 'Windows' osName: 'Windows'
imageName: ${{ variables.windowsImage }} imageName: 'windows-2019'
pool: pool:
vmImage: $(imageName) vmImage: $(imageName)
steps: steps:
- task: NodeTool@0 - task: NodeTool@0
displayName: Set Node.js version displayName: Set Node.js version
inputs: inputs:
versionSpec: $(nodeVersion) versionSpec: '10.x'
- checkout: self - checkout: self
submodules: true submodules: true
fetchDepth: 1 fetchDepth: 1
@@ -977,6 +766,7 @@ stages:
key: 'yarn | "$(osName)" | yarn.lock' key: 'yarn | "$(osName)" | yarn.lock'
restoreKeys: | restoreKeys: |
yarn | "$(osName)" yarn | "$(osName)"
yarn
path: $(yarnCacheFolder) path: $(yarnCacheFolder)
displayName: Cache Yarn packages displayName: Cache Yarn packages
- bash: ./build.sh --lint - bash: ./build.sh --lint
@@ -989,7 +779,7 @@ stages:
displayName: Frontend displayName: Frontend
condition: eq(variables['System.PullRequest.IsFork'], 'False') condition: eq(variables['System.PullRequest.IsFork'], 'False')
pool: pool:
vmImage: ${{ variables.windowsImage }} vmImage: windows-2019
steps: steps:
- checkout: self # Need history for Sonar analysis - checkout: self # Need history for Sonar analysis
- task: SonarCloudPrepare@1 - task: SonarCloudPrepare@1
@@ -1005,60 +795,6 @@ stages:
cliProjectVersion: '$(radarrVersion)' cliProjectVersion: '$(radarrVersion)'
cliSources: './frontend' cliSources: './frontend'
- task: SonarCloudAnalyze@1 - task: SonarCloudAnalyze@1
- job: Api_Docs
displayName: API Docs
dependsOn: Prepare
condition: |
and
(
and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop')),
and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
)
pool:
vmImage: ${{ variables.windowsImage }}
steps:
- task: UseDotNet@2
displayName: 'Install .net core'
inputs:
version: $(dotnetVersion)
- checkout: self
submodules: true
persistCredentials: true
fetchDepth: 1
- bash: ./docs.sh Windows
displayName: Create openapi.json
- bash: |
git config --global user.email "development@lidarr.audio"
git config --global user.name "Servarr"
git checkout -b api-docs
git add .
git status
if git status | grep modified
then
git commit -am 'Automated API Docs update'
git push -f --set-upstream origin api-docs
curl -X POST -H "Authorization: token ${GITHUBTOKEN}" -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/radarr/radarr/pulls -d '{"head":"api-docs","base":"develop","title":"Update API docs"}'
else
echo "No changes since last run"
fi
displayName: Commit API Doc Change
continueOnError: true
env:
GITHUBTOKEN: $(githubToken)
- task: CopyFiles@2
displayName: 'Copy openapi.json to: $(Build.ArtifactStagingDirectory)'
inputs:
SourceFolder: '$(Build.SourcesDirectory)'
Contents: |
**/*openapi.json
TargetFolder: '$(Build.ArtifactStagingDirectory)/api_docs'
- publish: $(Build.ArtifactStagingDirectory)/api_docs
artifact: 'APIDocs'
displayName: Publish API Docs Bundle
condition: and(succeeded(), eq(variables['System.JobAttempt'], '1'))
- job: Analyze_Backend - job: Analyze_Backend
displayName: Backend displayName: Backend
@@ -1067,10 +803,9 @@ stages:
variables: variables:
disable.coverage.autogenerate: 'true' disable.coverage.autogenerate: 'true'
EnableAnalyzers: 'false'
pool: pool:
vmImage: ${{ variables.windowsImage }} vmImage: windows-2019
steps: steps:
- task: UseDotNet@2 - task: UseDotNet@2
@@ -1092,12 +827,12 @@ stages:
projectVersion: '$(radarrVersion)' projectVersion: '$(radarrVersion)'
extraProperties: | extraProperties: |
sonar.exclusions=**/obj/**,**/*.dll,**/NzbDrone.Core.Test/Files/**/*,./frontend/**,**/ExternalModules/**,./src/Libraries/** sonar.exclusions=**/obj/**,**/*.dll,**/NzbDrone.Core.Test/Files/**/*,./frontend/**,**/ExternalModules/**,./src/Libraries/**
sonar.coverage.exclusions=**/Radarr.Api.V3/**/* sonar.coverage.exclusions=**/Radarr.Api.V3/**/*,**/NzbDrone.Api/**/*,**/MonoTorrent/**/*,**/Marr.Data/**/*
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: |
./build.sh --backend -f net6.0 -r win-x64 ./build.sh --backend -f netcoreapp3.1 -r win-x64
TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage TEST_DIR=_tests/netcoreapp3.1/win-x64/publish/ ./test.sh Windows Unit Coverage
displayName: Coverage Unit Tests displayName: Coverage Unit Tests
- task: SonarCloudAnalyze@1 - task: SonarCloudAnalyze@1
condition: eq(variables['System.PullRequest.IsFork'], 'False') condition: eq(variables['System.PullRequest.IsFork'], 'False')
@@ -1127,7 +862,7 @@ stages:
- job: - job:
displayName: Discord Notification displayName: Discord Notification
pool: pool:
vmImage: ${{ variables.linuxImage }} vmImage: 'windows-2019'
steps: steps:
- task: DownloadPipelineArtifact@2 - task: DownloadPipelineArtifact@2
continueOnError: true continueOnError: true
@@ -1137,11 +872,10 @@ stages:
artifactName: 'WindowsAutomationScreenshots' artifactName: 'WindowsAutomationScreenshots'
targetPath: $(Build.SourcesDirectory) targetPath: $(Build.SourcesDirectory)
- checkout: none - checkout: none
- pwsh: | - powershell: |
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)
DISCORDCHANNELID: $(discordChannelId) DISCORDCHANNELID: $(discordChannelId)
DISCORDWEBHOOKKEY: $(discordWebhookKey) DISCORDWEBHOOKKEY: $(discordWebhookKey)
DISCORDTHREADID: $(discordThreadId)
+37 -122
View File
@@ -1,4 +1,4 @@
#! /usr/bin/env bash #! /bin/bash
set -e set -e
outputFolder='_output' outputFolder='_output'
@@ -25,25 +25,6 @@ UpdateVersionNumber()
fi fi
} }
EnableExtraPlatformsInSDK()
{
SDK_PATH=$(dotnet --list-sdks | grep -P '6\.\d\.\d+' | head -1 | sed 's/\(6\.[0-9]*\.[0-9]*\).*\[\(.*\)\]/\2\/\1/g')
BUNDLEDVERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
if grep -q freebsd-x64 $BUNDLEDVERSIONS; then
echo "Extra platforms already enabled"
else
echo "Enabling extra platform support"
sed -i.ORI 's/osx-x64/osx-x64;freebsd-x64;linux-x86/' $BUNDLEDVERSIONS
fi
}
EnableExtraPlatforms()
{
if grep -qv freebsd-x64 src/Directory.Build.props; then
sed -i'' -e "s^<RuntimeIdentifiers>\(.*\)</RuntimeIdentifiers>^<RuntimeIdentifiers>\1;freebsd-x64;linux-x86</RuntimeIdentifiers>^g" src/Directory.Build.props
fi
}
LintUI() LintUI()
{ {
ProgressStart 'ESLint' ProgressStart 'ESLint'
@@ -94,11 +75,11 @@ YarnInstall()
ProgressEnd 'yarn install' ProgressEnd 'yarn install'
} }
RunWebpack() RunGulp()
{ {
ProgressStart 'Running webpack' ProgressStart 'Running gulp'
yarn run build --env production yarn run build --production
ProgressEnd 'Running webpack' ProgressEnd 'Running gulp'
} }
PackageFiles() PackageFiles()
@@ -137,7 +118,7 @@ PackageLinux()
echo "Adding Radarr.Mono to UpdatePackage" echo "Adding Radarr.Mono to UpdatePackage"
cp $folder/Radarr.Mono.* $folder/Radarr.Update cp $folder/Radarr.Mono.* $folder/Radarr.Update
if [ "$framework" = "net6.0" ]; then if [ "$framework" = "netcoreapp3.1" ]; then
cp $folder/Mono.Posix.NETStandard.* $folder/Radarr.Update cp $folder/Mono.Posix.NETStandard.* $folder/Radarr.Update
cp $folder/libMonoPosixHelper.* $folder/Radarr.Update cp $folder/libMonoPosixHelper.* $folder/Radarr.Update
fi fi
@@ -148,13 +129,17 @@ PackageLinux()
PackageMacOS() PackageMacOS()
{ {
local framework="$1" local framework="$1"
local runtime="$2"
ProgressStart "Creating MacOS Package for $framework $runtime" ProgressStart "Creating MacOS Package for $framework"
local folder=$artifactsFolder/$runtime/$framework/Radarr local folder=$artifactsFolder/macos/$framework/Radarr
PackageFiles "$folder" "$framework" "$runtime" PackageFiles "$folder" "$framework" "osx-x64"
if [ "$framework" = "net462" ]; 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.*
@@ -165,7 +150,7 @@ PackageMacOS()
echo "Adding Radarr.Mono to UpdatePackage" echo "Adding Radarr.Mono to UpdatePackage"
cp $folder/Radarr.Mono.* $folder/Radarr.Update cp $folder/Radarr.Mono.* $folder/Radarr.Update
if [ "$framework" = "net6.0" ]; then if [ "$framework" = "netcoreapp3.1" ]; then
cp $folder/Mono.Posix.NETStandard.* $folder/Radarr.Update cp $folder/Mono.Posix.NETStandard.* $folder/Radarr.Update
cp $folder/libMonoPosixHelper.* $folder/Radarr.Update cp $folder/libMonoPosixHelper.* $folder/Radarr.Update
fi fi
@@ -176,11 +161,10 @@ PackageMacOS()
PackageMacOSApp() PackageMacOSApp()
{ {
local framework="$1" local framework="$1"
local runtime="$2"
ProgressStart "Creating macOS App Package for $framework $runtime" ProgressStart "Creating macOS App Package for $framework"
local folder="$artifactsFolder/$runtime-app/$framework" local folder=$artifactsFolder/macos-app/$framework
rm -rf $folder rm -rf $folder
mkdir -p $folder mkdir -p $folder
@@ -188,7 +172,7 @@ PackageMacOSApp()
mkdir -p $folder/Radarr.app/Contents/MacOS mkdir -p $folder/Radarr.app/Contents/MacOS
echo "Copying Binaries" echo "Copying Binaries"
cp -r $artifactsFolder/$runtime/$framework/Radarr/* $folder/Radarr.app/Contents/MacOS cp -r $artifactsFolder/macos/$framework/Radarr/* $folder/Radarr.app/Contents/MacOS
echo "Removing Update Folder" echo "Removing Update Folder"
rm -r $folder/Radarr.app/Contents/MacOS/Radarr.Update rm -r $folder/Radarr.app/Contents/MacOS/Radarr.Update
@@ -206,7 +190,6 @@ PackageWindows()
local folder=$artifactsFolder/$runtime/$framework/Radarr local folder=$artifactsFolder/$runtime/$framework/Radarr
PackageFiles "$folder" "$framework" "$runtime" PackageFiles "$folder" "$framework" "$runtime"
cp -r $outputFolder/$framework-windows/$runtime/publish/* $folder
echo "Removing Radarr.Mono" echo "Removing Radarr.Mono"
rm -f $folder/Radarr.Mono.* rm -f $folder/Radarr.Mono.*
@@ -228,45 +211,19 @@ Package()
IFS='-' read -ra SPLIT <<< "$runtime" IFS='-' read -ra SPLIT <<< "$runtime"
case "${SPLIT[0]}" in case "${SPLIT[0]}" in
linux|freebsd*) linux)
PackageLinux "$framework" "$runtime" PackageLinux "$framework" "$runtime"
;; ;;
win) win)
PackageWindows "$framework" "$runtime" PackageWindows "$framework" "$runtime"
;; ;;
osx) osx)
PackageMacOS "$framework" "$runtime" PackageMacOS "$framework"
PackageMacOSApp "$framework" "$runtime" PackageMacOSApp "$framework"
;; ;;
esac esac
} }
BuildInstaller()
{
local framework="$1"
local runtime="$2"
./_inno/ISCC.exe setup/radarr.iss "//DFramework=$framework" "//DRuntime=$runtime"
}
InstallInno()
{
ProgressStart "Installing portable Inno Setup"
rm -rf _inno
curl -s --output innosetup.exe "https://files.jrsoftware.org/is/6/innosetup-${INNOVERSION:-6.2.0}.exe"
mkdir _inno
./innosetup.exe //portable=1 //silent //currentuser //dir=.\\_inno
rm innosetup.exe
ProgressEnd "Installed portable Inno Setup"
}
RemoveInno()
{
rm -rf _inno
}
PackageTests() PackageTests()
{ {
local framework="$1" local framework="$1"
@@ -298,10 +255,7 @@ if [ $# -eq 0 ]; then
BACKEND=YES BACKEND=YES
FRONTEND=YES FRONTEND=YES
PACKAGES=YES PACKAGES=YES
INSTALLER=NO
LINT=YES LINT=YES
ENABLE_EXTRA_PLATFORMS=NO
ENABLE_EXTRA_PLATFORMS_IN_SDK=NO
fi fi
while [[ $# -gt 0 ]] while [[ $# -gt 0 ]]
@@ -313,14 +267,6 @@ case $key in
BACKEND=YES BACKEND=YES
shift # past argument shift # past argument
;; ;;
--enable-bsd|--enable-extra-platforms)
ENABLE_EXTRA_PLATFORMS=YES
shift # past argument
;;
--enable-extra-platforms-in-sdk)
ENABLE_EXTRA_PLATFORMS_IN_SDK=YES
shift # past argument
;;
-r|--runtime) -r|--runtime)
RID="$2" RID="$2"
shift # past argument shift # past argument
@@ -339,10 +285,6 @@ case $key in
PACKAGES=YES PACKAGES=YES
shift # past argument shift # past argument
;; ;;
--installer)
INSTALLER=YES
shift # past argument
;;
--lint) --lint)
LINT=YES LINT=YES
shift # past argument shift # past argument
@@ -362,31 +304,18 @@ esac
done done
set -- "${POSITIONAL[@]}" # restore positional parameters set -- "${POSITIONAL[@]}" # restore positional parameters
if [ "$ENABLE_EXTRA_PLATFORMS_IN_SDK" = "YES" ];
then
EnableExtraPlatformsInSDK
fi
if [ "$BACKEND" = "YES" ]; if [ "$BACKEND" = "YES" ];
then then
UpdateVersionNumber UpdateVersionNumber
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
then
EnableExtraPlatforms
fi
Build Build
if [[ -z "$RID" || -z "$FRAMEWORK" ]]; if [[ -z "$RID" || -z "$FRAMEWORK" ]];
then then
PackageTests "net6.0" "win-x64" PackageTests "netcoreapp3.1" "win-x64"
PackageTests "net6.0" "win-x86" PackageTests "netcoreapp3.1" "win-x86"
PackageTests "net6.0" "linux-x64" PackageTests "netcoreapp3.1" "linux-x64"
PackageTests "net6.0" "linux-musl-x64" PackageTests "netcoreapp3.1" "linux-musl-x64"
PackageTests "net6.0" "osx-x64" PackageTests "netcoreapp3.1" "osx-x64"
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ]; PackageTests "net462" "linux-x64"
then
PackageTests "net6.0" "freebsd-x64"
PackageTests "net6.0" "linux-x86"
fi
else else
PackageTests "$FRAMEWORK" "$RID" PackageTests "$FRAMEWORK" "$RID"
fi fi
@@ -395,7 +324,7 @@ fi
if [ "$FRONTEND" = "YES" ]; if [ "$FRONTEND" = "YES" ];
then then
YarnInstall YarnInstall
RunWebpack RunGulp
fi fi
if [ "$LINT" = "YES" ]; if [ "$LINT" = "YES" ];
@@ -414,30 +343,16 @@ then
if [[ -z "$RID" || -z "$FRAMEWORK" ]]; if [[ -z "$RID" || -z "$FRAMEWORK" ]];
then then
Package "net6.0" "win-x64" Package "netcoreapp3.1" "win-x64"
Package "net6.0" "win-x86" Package "netcoreapp3.1" "win-x86"
Package "net6.0" "linux-x64" Package "netcoreapp3.1" "linux-x64"
Package "net6.0" "linux-musl-x64" Package "netcoreapp3.1" "linux-musl-x64"
Package "net6.0" "linux-arm64" Package "netcoreapp3.1" "linux-arm64"
Package "net6.0" "linux-musl-arm64" Package "netcoreapp3.1" "linux-musl-arm64"
Package "net6.0" "linux-arm" Package "netcoreapp3.1" "linux-arm"
Package "net6.0" "linux-musl-arm" Package "netcoreapp3.1" "osx-x64"
Package "net6.0" "osx-x64" Package "net462" "linux-x64"
Package "net6.0" "osx-arm64"
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
then
Package "net6.0" "freebsd-x64"
Package "net6.0" "linux-x86"
fi
else else
Package "$FRAMEWORK" "$RID" Package "$FRAMEWORK" "$RID"
fi fi
fi fi
if [ "$INSTALLER" = "YES" ];
then
InstallInno
BuildInstaller "net6.0" "win-x64"
BuildInstaller "net6.0" "win-x86"
RemoveInno
fi
-38
View File
@@ -1,38 +0,0 @@
PLATFORM=$1
if [ "$PLATFORM" = "Windows" ]; then
RUNTIME="win-x64"
elif [ "$PLATFORM" = "Linux" ]; then
WHERE="linux-x64"
elif [ "$PLATFORM" = "Mac" ]; then
WHERE="osx-x64"
else
echo "Platform must be provided as first arguement: Windows, Linux or Mac"
exit 1
fi
outputFolder='_output'
testPackageFolder='_tests'
rm -rf $outputFolder
rm -rf $testPackageFolder
slnFile=src/Radarr.sln
platform=Posix
dotnet clean $slnFile -c Debug
dotnet clean $slnFile -c Release
dotnet msbuild -restore $slnFile -p:Configuration=Debug -p:Platform=$platform -p:RuntimeIdentifiers=$RUNTIME -t:PublishAllRids
dotnet new tool-manifest
dotnet tool install --version 6.3.0 Swashbuckle.AspNetCore.Cli
dotnet tool run swagger tofile --output ./src/Radarr.Api.V3/openapi.json "$outputFolder/net6.0/$RUNTIME/radarr.console.dll" v3 &
sleep 45
kill %1
exit 0
+4 -12
View File
@@ -6,10 +6,8 @@ const dirs = fs
.map((dirent) => dirent.name) .map((dirent) => dirent.name)
.join('|'); .join('|');
const frontendFolder = __dirname;
module.exports = { module.exports = {
parser: '@babel/eslint-parser', parser: 'babel-eslint',
env: { env: {
browser: true, browser: true,
@@ -27,9 +25,6 @@ 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
@@ -39,7 +34,6 @@ module.exports = {
plugins: [ plugins: [
'filenames', 'filenames',
'react', 'react',
'react-hooks',
'simple-import-sort', 'simple-import-sort',
'import' 'import'
], ],
@@ -277,7 +271,7 @@ module.exports = {
// ImportSort // ImportSort
'simple-import-sort/imports': 'error', 'simple-import-sort/sort': 'error',
'import/newline-after-import': 'error', 'import/newline-after-import': 'error',
// React // React
@@ -309,15 +303,13 @@ module.exports = {
'react/react-in-jsx-scope': 2, 'react/react-in-jsx-scope': 2,
'react/self-closing-comp': 2, 'react/self-closing-comp': 2,
'react/sort-comp': 2, 'react/sort-comp': 2,
'react/jsx-wrap-multilines': 2, 'react/jsx-wrap-multilines': 2
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'error'
}, },
overrides: [ overrides: [
{ {
files: ['*.js'], files: ['*.js'],
rules: { rules: {
'simple-import-sort/imports': [ 'simple-import-sort/sort': [
'error', 'error',
{ {
groups: [ groups: [
-269
View File
@@ -1,269 +0,0 @@
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: {
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;
};
+18
View File
@@ -0,0 +1,18 @@
const gulp = require('gulp');
require('./clean');
require('./copy');
require('./webpack');
gulp.task('build',
gulp.series('clean',
gulp.parallel(
'webpack',
'copyHtml',
'copyFonts',
'copyImages',
'copyRobots'
)
)
);
+8
View File
@@ -0,0 +1,8 @@
const gulp = require('gulp');
const del = require('del');
const paths = require('./helpers/paths');
gulp.task('clean', () => {
return del([paths.dest.root]);
});
+42
View File
@@ -0,0 +1,42 @@
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());
});
+5
View File
@@ -0,0 +1,5 @@
require('./build.js');
require('./clean.js');
require('./copy.js');
require('./watch.js');
require('./webpack.js');
+6
View File
@@ -0,0 +1,6 @@
const colors = require('ansi-colors');
module.exports = function errorHandler(error) {
console.log(colors.red(`Error (${error.plugin}): ${error.message}`));
this.emit('end');
};
+24
View File
@@ -0,0 +1,24 @@
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;
+19
View File
@@ -0,0 +1,19 @@
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));
+271
View File
@@ -0,0 +1,271 @@
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 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);
});
+19 -27
View File
@@ -1,31 +1,23 @@
const reload = require('require-nocache')(module); const reload = require('require-nocache')(module);
const cssVarsFiles = [ module.exports = (ctx, configPath, options) => {
'./src/Styles/Variables/dimensions', const config = {
'./src/Styles/Variables/fonts', plugins: {
'./src/Styles/Variables/animations', 'postcss-mixins': {
'./src/Styles/Variables/zIndexes' mixinsDir: [
].map(require.resolve); 'frontend/src/Styles/Mixins'
]
},
'postcss-simple-vars': {
variables: () =>
ctx.options.cssVarsFiles.reduce((acc, vars) => {
return Object.assign(acc, reload(vars));
}, {})
},
'postcss-color-function': {},
'postcss-nested': {}
}
};
const mixinsFiles = [ return config;
'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'
]
}; };
@@ -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 BlocklistRowConnector from './BlocklistRowConnector'; import BlacklistRowConnector from './BlacklistRowConnector';
class Blocklist extends Component { class Blacklist extends Component {
// //
// Lifecycle // Lifecycle
@@ -61,33 +61,33 @@ class Blocklist extends Component {
getSelectedIds = () => { getSelectedIds = () => {
return getSelectedIds(this.state.selectedState); return getSelectedIds(this.state.selectedState);
}; }
// //
// Listeners // Listeners
onSelectAllChange = ({ value }) => { onSelectAllChange = ({ value }) => {
this.setState(selectAll(this.state.selectedState, value)); this.setState(selectAll(this.state.selectedState, value));
}; }
onSelectedChange = ({ id, value, shiftKey = false }) => { onSelectedChange = ({ id, value, shiftKey = false }) => {
this.setState((state) => { this.setState((state) => {
return toggleSelected(state, this.props.items, id, value, shiftKey); return toggleSelected(state, this.props.items, id, value, shiftKey);
}); });
}; }
onRemoveSelectedPress = () => { onRemoveSelectedPress = () => {
this.setState({ isConfirmRemoveModalOpen: true }); this.setState({ isConfirmRemoveModalOpen: true });
}; }
onRemoveSelectedConfirmed = () => { onRemoveSelectedConfirmed = () => {
this.props.onRemoveSelected(this.getSelectedIds()); this.props.onRemoveSelected(this.getSelectedIds());
this.setState({ isConfirmRemoveModalOpen: false }); this.setState({ isConfirmRemoveModalOpen: false });
}; }
onConfirmRemoveModalClose = () => { onConfirmRemoveModalClose = () => {
this.setState({ isConfirmRemoveModalOpen: false }); this.setState({ isConfirmRemoveModalOpen: false });
}; }
// //
// Render // Render
@@ -101,8 +101,8 @@ class Blocklist extends Component {
columns, columns,
totalRecords, totalRecords,
isRemoving, isRemoving,
isClearingBlocklistExecuting, isClearingBlacklistExecuting,
onClearBlocklistPress, onClearBlacklistPress,
...otherProps ...otherProps
} = this.props; } = this.props;
@@ -116,11 +116,11 @@ class Blocklist extends Component {
const selectedIds = this.getSelectedIds(); const selectedIds = this.getSelectedIds();
return ( return (
<PageContent title={translate('Blocklist')}> <PageContent title={translate('Blacklist')}>
<PageToolbar> <PageToolbar>
<PageToolbarSection> <PageToolbarSection>
<PageToolbarButton <PageToolbarButton
label={translate('RemoveSelected')} label="Remove Selected"
iconName={icons.REMOVE} iconName={icons.REMOVE}
isDisabled={!selectedIds.length} isDisabled={!selectedIds.length}
isSpinning={isRemoving} isSpinning={isRemoving}
@@ -130,8 +130,8 @@ class Blocklist extends Component {
<PageToolbarButton <PageToolbarButton
label={translate('Clear')} label={translate('Clear')}
iconName={icons.CLEAR} iconName={icons.CLEAR}
isSpinning={isClearingBlocklistExecuting} isSpinning={isClearingBlacklistExecuting}
onPress={onClearBlocklistPress} onPress={onClearBlacklistPress}
/> />
</PageToolbarSection> </PageToolbarSection>
@@ -157,7 +157,7 @@ class Blocklist extends Component {
{ {
!isFetching && !!error && !isFetching && !!error &&
<div> <div>
{translate('UnableToLoadBlocklist')} {translate('UnableToLoadBlacklist')}
</div> </div>
} }
@@ -183,7 +183,7 @@ class Blocklist extends Component {
{ {
items.map((item) => { items.map((item) => {
return ( return (
<BlocklistRowConnector <BlacklistRowConnector
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 Blocklist extends Component {
isOpen={isConfirmRemoveModalOpen} isOpen={isConfirmRemoveModalOpen}
kind={kinds.DANGER} kind={kinds.DANGER}
title={translate('RemoveSelected')} title={translate('RemoveSelected')}
message={translate('AreYouSureYouWantToRemoveTheSelectedItemsFromBlocklist')} message={translate('AreYouSureYouWantToRemoveTheSelectedItemsFromBlacklist')}
confirmLabel={translate('RemoveSelected')} confirmLabel={translate('RemoveSelected')}
onConfirm={this.onRemoveSelectedConfirmed} onConfirm={this.onRemoveSelectedConfirmed}
onCancel={this.onConfirmRemoveModalClose} onCancel={this.onConfirmRemoveModalClose}
@@ -219,7 +219,7 @@ class Blocklist extends Component {
} }
} }
Blocklist.propTypes = { Blacklist.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 @@ Blocklist.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,
isClearingBlocklistExecuting: PropTypes.bool.isRequired, isClearingBlacklistExecuting: PropTypes.bool.isRequired,
onRemoveSelected: PropTypes.func.isRequired, onRemoveSelected: PropTypes.func.isRequired,
onClearBlocklistPress: PropTypes.func.isRequired onClearBlacklistPress: PropTypes.func.isRequired
}; };
export default Blocklist; export default Blacklist;
@@ -0,0 +1,160 @@
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)
);
@@ -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 BlocklistDetailsModal extends Component { class BlacklistDetailsModal extends Component {
// //
// Render // Render
@@ -78,7 +78,7 @@ class BlocklistDetailsModal extends Component {
} }
} }
BlocklistDetailsModal.propTypes = { BlacklistDetailsModal.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 @@ BlocklistDetailsModal.propTypes = {
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired
}; };
export default BlocklistDetailsModal; export default BlacklistDetailsModal;
@@ -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 BlocklistDetailsModal from './BlocklistDetailsModal'; import BlacklistDetailsModal from './BlacklistDetailsModal';
import styles from './BlocklistRow.css'; import styles from './BlacklistRow.css';
class BlocklistRow extends Component { class BlacklistRow extends Component {
// //
// Lifecycle // Lifecycle
@@ -32,11 +32,11 @@ class BlocklistRow extends Component {
onDetailsPress = () => { onDetailsPress = () => {
this.setState({ isDetailsModalOpen: true }); this.setState({ isDetailsModalOpen: true });
}; }
onDetailsModalClose = () => { onDetailsModalClose = () => {
this.setState({ isDetailsModalOpen: false }); this.setState({ isDetailsModalOpen: false });
}; }
// //
// Render // Render
@@ -166,7 +166,7 @@ class BlocklistRow extends Component {
/> />
<IconButton <IconButton
title={translate('RemoveFromBlocklist')} title={translate('RemoveFromBlacklist')}
name={icons.REMOVE} name={icons.REMOVE}
kind={kinds.DANGER} kind={kinds.DANGER}
onPress={onRemovePress} onPress={onRemovePress}
@@ -179,7 +179,7 @@ class BlocklistRow extends Component {
}) })
} }
<BlocklistDetailsModal <BlacklistDetailsModal
isOpen={this.state.isDetailsModalOpen} isOpen={this.state.isDetailsModalOpen}
sourceTitle={sourceTitle} sourceTitle={sourceTitle}
protocol={protocol} protocol={protocol}
@@ -193,7 +193,7 @@ class BlocklistRow extends Component {
} }
BlocklistRow.propTypes = { BlacklistRow.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 @@ BlocklistRow.propTypes = {
onRemovePress: PropTypes.func.isRequired onRemovePress: PropTypes.func.isRequired
}; };
export default BlocklistRow; export default BlacklistRow;
@@ -1,8 +1,8 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { removeBlocklistItem } from 'Store/Actions/blocklistActions'; import { removeBlacklistItem } from 'Store/Actions/blacklistActions';
import createMovieSelector from 'Store/Selectors/createMovieSelector'; import createMovieSelector from 'Store/Selectors/createMovieSelector';
import BlocklistRow from './BlocklistRow'; import BlacklistRow from './BlacklistRow';
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(removeBlocklistItem({ id: props.id })); dispatch(removeBlacklistItem({ id: props.id }));
} }
}; };
} }
export default connect(createMapStateToProps, createMapDispatchToProps)(BlocklistRow); export default connect(createMapStateToProps, createMapDispatchToProps)(BlacklistRow);
@@ -1,152 +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 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)
);
@@ -25,7 +25,6 @@ function HistoryDetails(props) {
releaseGroup, releaseGroup,
nzbInfoUrl, nzbInfoUrl,
downloadClient, downloadClient,
downloadClientName,
downloadId, downloadId,
age, age,
ageHours, ageHours,
@@ -33,8 +32,6 @@ function HistoryDetails(props) {
publishedDate publishedDate
} = data; } = data;
const downloadClientNameInfo = downloadClientName ?? downloadClient;
return ( return (
<DescriptionList> <DescriptionList>
<DescriptionListItem <DescriptionListItem
@@ -74,12 +71,11 @@ function HistoryDetails(props) {
} }
{ {
downloadClientNameInfo ? !!downloadClient &&
<DescriptionListItem <DescriptionListItem
title={translate('DownloadClient')} title={translate('DownloadClient')}
data={downloadClientNameInfo} data={downloadClient}
/> : />
null
} }
{ {
@@ -57,38 +57,38 @@ class HistoryConnector extends Component {
repopulate = () => { repopulate = () => {
this.props.fetchHistory(); this.props.fetchHistory();
}; }
// //
// Listeners // Listeners
onFirstPagePress = () => { onFirstPagePress = () => {
this.props.gotoHistoryFirstPage(); this.props.gotoHistoryFirstPage();
}; }
onPreviousPagePress = () => { onPreviousPagePress = () => {
this.props.gotoHistoryPreviousPage(); this.props.gotoHistoryPreviousPage();
}; }
onNextPagePress = () => { onNextPagePress = () => {
this.props.gotoHistoryNextPage(); this.props.gotoHistoryNextPage();
}; }
onLastPagePress = () => { onLastPagePress = () => {
this.props.gotoHistoryLastPage(); this.props.gotoHistoryLastPage();
}; }
onPageSelect = (page) => { onPageSelect = (page) => {
this.props.gotoHistoryPage({ page }); this.props.gotoHistoryPage({ page });
}; }
onSortPress = (sortKey) => { onSortPress = (sortKey) => {
this.props.setHistorySort({ sortKey }); this.props.setHistorySort({ sortKey });
}; }
onFilterSelect = (selectedFilterKey) => { onFilterSelect = (selectedFilterKey) => {
this.props.setHistoryFilter({ selectedFilterKey }); this.props.setHistoryFilter({ selectedFilterKey });
}; }
onTableOptionChange = (payload) => { onTableOptionChange = (payload) => {
this.props.setHistoryTableOption(payload); this.props.setHistoryTableOption(payload);
@@ -96,7 +96,7 @@ class HistoryConnector extends Component {
if (payload.pageSize) { if (payload.pageSize) {
this.props.gotoHistoryFirstPage(); this.props.gotoHistoryFirstPage();
} }
}; }
// //
// Render // Render
@@ -10,12 +10,6 @@
width: 80px; width: 80px;
} }
.customFormatScore {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 55px;
}
.releaseGroup { .releaseGroup {
composes: cell from '~Components/Table/Cells/TableRowCell.css'; composes: cell from '~Components/Table/Cells/TableRowCell.css';
+2 -24
View File
@@ -9,7 +9,6 @@ import MovieFormats from 'Movie/MovieFormats';
import MovieLanguage from 'Movie/MovieLanguage'; import MovieLanguage from 'Movie/MovieLanguage';
import MovieQuality from 'Movie/MovieQuality'; import MovieQuality from 'Movie/MovieQuality';
import MovieTitleLink from 'Movie/MovieTitleLink'; import MovieTitleLink from 'Movie/MovieTitleLink';
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
import HistoryDetailsModal from './Details/HistoryDetailsModal'; import HistoryDetailsModal from './Details/HistoryDetailsModal';
import HistoryEventTypeCell from './HistoryEventTypeCell'; import HistoryEventTypeCell from './HistoryEventTypeCell';
import styles from './HistoryRow.css'; import styles from './HistoryRow.css';
@@ -42,11 +41,11 @@ class HistoryRow extends Component {
onDetailsPress = () => { onDetailsPress = () => {
this.setState({ isDetailsModalOpen: true }); this.setState({ isDetailsModalOpen: true });
}; }
onDetailsModalClose = () => { onDetailsModalClose = () => {
this.setState({ isDetailsModalOpen: false }); this.setState({ isDetailsModalOpen: false });
}; }
// //
// Render // Render
@@ -169,17 +168,6 @@ class HistoryRow extends Component {
); );
} }
if (name === 'customFormatScore') {
return (
<TableRowCell
key={name}
className={styles.customFormatScore}
>
{formatCustomFormatScore(data.customFormatScore)}
</TableRowCell>
);
}
if (name === 'releaseGroup') { if (name === 'releaseGroup') {
return ( return (
<TableRowCell <TableRowCell
@@ -191,16 +179,6 @@ class HistoryRow extends Component {
); );
} }
if (name === 'sourceTitle') {
return (
<TableRowCell
key={name}
>
{sourceTitle}
</TableRowCell>
);
}
if (name === 'details') { if (name === 'details') {
return ( return (
<TableRowCell <TableRowCell
@@ -46,7 +46,7 @@ class HistoryRowConnector extends Component {
onMarkAsFailedPress = () => { onMarkAsFailedPress = () => {
this.props.markAsFailed({ id: this.props.id }); this.props.markAsFailed({ id: this.props.id });
}; }
// //
// Render // Render
@@ -1,13 +1,13 @@
.torrent { .torrent {
composes: label from '~Components/Label.css'; composes: label from '~Components/Label.css';
border-color: var(--torrentColor); border-color: $torrentColor;
background-color: var(--torrentColor); background-color: $torrentColor;
} }
.usenet { .usenet {
composes: label from '~Components/Label.css'; composes: label from '~Components/Label.css';
border-color: var(--usenetColor); border-color: $usenetColor;
background-color: var(--usenetColor); background-color: $usenetColor;
} }
+17 -61
View File
@@ -32,8 +32,6 @@ class Queue extends Component {
constructor(props, context) { constructor(props, context) {
super(props, context); super(props, context);
this._shouldBlockRefresh = false;
this.state = { this.state = {
allSelected: false, allSelected: false,
allUnselected: false, allUnselected: false,
@@ -45,14 +43,6 @@ class Queue extends Component {
}; };
} }
shouldComponentUpdate() {
if (this._shouldBlockRefresh) {
return false;
}
return true;
}
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
const { const {
items, items,
@@ -75,23 +65,13 @@ class Queue extends Component {
return; return;
} }
const nextState = {};
if (prevProps.items !== items) {
nextState.items = items;
}
const selectedIds = this.getSelectedIds(); const selectedIds = this.getSelectedIds();
const isPendingSelected = _.some(this.props.items, (item) => { const isPendingSelected = _.some(this.props.items, (item) => {
return selectedIds.indexOf(item.id) > -1 && item.status === 'delay'; return selectedIds.indexOf(item.id) > -1 && item.status === 'delay';
}); });
if (isPendingSelected !== this.state.isPendingSelected) { if (isPendingSelected !== this.state.isPendingSelected) {
nextState.isPendingSelected = isPendingSelected; this.setState({ isPendingSelected });
}
if (!_.isEmpty(nextState)) {
this.setState(nextState);
} }
} }
@@ -100,45 +80,37 @@ class Queue extends Component {
getSelectedIds = () => { getSelectedIds = () => {
return getSelectedIds(this.state.selectedState); return getSelectedIds(this.state.selectedState);
}; }
// //
// Listeners // Listeners
onQueueRowModalOpenOrClose = (isOpen) => {
this._shouldBlockRefresh = isOpen;
};
onSelectAllChange = ({ value }) => { onSelectAllChange = ({ value }) => {
this.setState(selectAll(this.state.selectedState, value)); this.setState(selectAll(this.state.selectedState, value));
}; }
onSelectedChange = ({ id, value, shiftKey = false }) => { onSelectedChange = ({ id, value, shiftKey = false }) => {
this.setState((state) => { this.setState((state) => {
return toggleSelected(state, this.props.items, id, value, shiftKey); return toggleSelected(state, this.props.items, id, value, shiftKey);
}); });
}; }
onGrabSelectedPress = () => { onGrabSelectedPress = () => {
this.props.onGrabSelectedPress(this.getSelectedIds()); this.props.onGrabSelectedPress(this.getSelectedIds());
}; }
onRemoveSelectedPress = () => { onRemoveSelectedPress = () => {
this.setState({ isConfirmRemoveModalOpen: true }, () => { this.setState({ isConfirmRemoveModalOpen: true });
this._shouldBlockRefresh = true; }
});
};
onRemoveSelectedConfirmed = (payload) => { onRemoveSelectedConfirmed = (payload) => {
this._shouldBlockRefresh = false;
this.props.onRemoveSelectedPress({ ids: this.getSelectedIds(), ...payload }); this.props.onRemoveSelectedPress({ ids: this.getSelectedIds(), ...payload });
this.setState({ isConfirmRemoveModalOpen: false }); this.setState({ isConfirmRemoveModalOpen: false });
}; }
onConfirmRemoveModalClose = () => { onConfirmRemoveModalClose = () => {
this._shouldBlockRefresh = false;
this.setState({ isConfirmRemoveModalOpen: false }); this.setState({ isConfirmRemoveModalOpen: false });
}; }
// //
// Render // Render
@@ -224,29 +196,26 @@ class Queue extends Component {
<PageContentBody> <PageContentBody>
{ {
isRefreshing && !isAllPopulated ? isRefreshing && !isAllPopulated &&
<LoadingIndicator /> : <LoadingIndicator />
null
} }
{ {
!isRefreshing && hasError ? !isRefreshing && hasError &&
<div> <div>
{translate('FailedToLoadQueue')} {translate('FailedToLoadQueue')}
</div> : </div>
null
} }
{ {
isAllPopulated && !hasError && !items.length ? isPopulated && !hasError && !items.length &&
<div> <div>
{translate('QueueIsEmpty')} {translate('QueueIsEmpty')}
</div> : </div>
null
} }
{ {
isAllPopulated && !hasError && !!items.length ? isAllPopulated && !hasError && !!items.length &&
<div> <div>
<Table <Table
columns={columns} columns={columns}
@@ -268,7 +237,6 @@ class Queue extends Component {
columns={columns} columns={columns}
{...item} {...item}
onSelectedChange={this.onSelectedChange} onSelectedChange={this.onSelectedChange}
onQueueRowModalOpenOrClose={this.onQueueRowModalOpenOrClose}
/> />
); );
}) })
@@ -281,8 +249,7 @@ class Queue extends Component {
isFetching={isRefreshing} isFetching={isRefreshing}
{...otherProps} {...otherProps}
/> />
</div> : </div>
null
} }
</PageContentBody> </PageContentBody>
@@ -296,17 +263,6 @@ class Queue extends Component {
return !!(item && item.movieId); return !!(item && item.movieId);
}) })
)} )}
allPending={isConfirmRemoveModalOpen && (
selectedIds.every((id) => {
const item = items.find((i) => i.id === id);
if (!item) {
return false;
}
return item.status === 'delay' || item.status === 'downloadClientUnavailable';
})
)}
onRemovePress={this.onRemoveSelectedConfirmed} onRemovePress={this.onRemoveSelectedConfirmed}
onModalClose={this.onConfirmRemoveModalClose} onModalClose={this.onConfirmRemoveModalClose}
/> />
+11 -15
View File
@@ -43,7 +43,6 @@ class QueueConnector extends Component {
const { const {
useCurrentPage, useCurrentPage,
fetchQueue, fetchQueue,
fetchQueueStatus,
gotoQueueFirstPage gotoQueueFirstPage
} = this.props; } = this.props;
@@ -54,8 +53,6 @@ class QueueConnector extends Component {
} else { } else {
gotoQueueFirstPage(); gotoQueueFirstPage();
} }
fetchQueueStatus();
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
@@ -77,34 +74,34 @@ class QueueConnector extends Component {
repopulate = () => { repopulate = () => {
this.props.fetchQueue(); this.props.fetchQueue();
}; }
// //
// Listeners // Listeners
onFirstPagePress = () => { onFirstPagePress = () => {
this.props.gotoQueueFirstPage(); this.props.gotoQueueFirstPage();
}; }
onPreviousPagePress = () => { onPreviousPagePress = () => {
this.props.gotoQueuePreviousPage(); this.props.gotoQueuePreviousPage();
}; }
onNextPagePress = () => { onNextPagePress = () => {
this.props.gotoQueueNextPage(); this.props.gotoQueueNextPage();
}; }
onLastPagePress = () => { onLastPagePress = () => {
this.props.gotoQueueLastPage(); this.props.gotoQueueLastPage();
}; }
onPageSelect = (page) => { onPageSelect = (page) => {
this.props.gotoQueuePage({ page }); this.props.gotoQueuePage({ page });
}; }
onSortPress = (sortKey) => { onSortPress = (sortKey) => {
this.props.setQueueSort({ sortKey }); this.props.setQueueSort({ sortKey });
}; }
onTableOptionChange = (payload) => { onTableOptionChange = (payload) => {
this.props.setQueueTableOption(payload); this.props.setQueueTableOption(payload);
@@ -112,21 +109,21 @@ class QueueConnector extends Component {
if (payload.pageSize) { if (payload.pageSize) {
this.props.gotoQueueFirstPage(); this.props.gotoQueueFirstPage();
} }
}; }
onRefreshPress = () => { onRefreshPress = () => {
this.props.executeCommand({ this.props.executeCommand({
name: commandNames.REFRESH_MONITORED_DOWNLOADS name: commandNames.REFRESH_MONITORED_DOWNLOADS
}); });
}; }
onGrabSelectedPress = (ids) => { onGrabSelectedPress = (ids) => {
this.props.grabQueueItems({ ids }); this.props.grabQueueItems({ ids });
}; }
onRemoveSelectedPress = (payload) => { onRemoveSelectedPress = (payload) => {
this.props.removeQueueItems(payload); this.props.removeQueueItems(payload);
}; }
// //
// Render // Render
@@ -155,7 +152,6 @@ QueueConnector.propTypes = {
useCurrentPage: PropTypes.bool.isRequired, useCurrentPage: PropTypes.bool.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
fetchQueue: PropTypes.func.isRequired, fetchQueue: PropTypes.func.isRequired,
fetchQueueStatus: PropTypes.func.isRequired,
gotoQueueFirstPage: PropTypes.func.isRequired, gotoQueueFirstPage: PropTypes.func.isRequired,
gotoQueuePreviousPage: PropTypes.func.isRequired, gotoQueuePreviousPage: PropTypes.func.isRequired,
gotoQueueNextPage: PropTypes.func.isRequired, gotoQueueNextPage: PropTypes.func.isRequired,
+1 -1
View File
@@ -42,7 +42,7 @@ class QueueOptions extends Component {
[name]: value [name]: value
}); });
}); });
}; }
// //
// Render // Render
+8 -23
View File
@@ -40,37 +40,24 @@ class QueueRow extends Component {
onRemoveQueueItemPress = () => { onRemoveQueueItemPress = () => {
this.setState({ isRemoveQueueItemModalOpen: true }); this.setState({ isRemoveQueueItemModalOpen: true });
}; }
onRemoveQueueItemModalConfirmed = (blocklist) => {
const {
onRemoveQueueItemPress,
onQueueRowModalOpenOrClose
} = this.props;
onQueueRowModalOpenOrClose(false);
onRemoveQueueItemPress(blocklist);
onRemoveQueueItemModalConfirmed = (blacklist) => {
this.props.onRemoveQueueItemPress(blacklist);
this.setState({ isRemoveQueueItemModalOpen: false }); this.setState({ isRemoveQueueItemModalOpen: false });
}; }
onRemoveQueueItemModalClose = () => { onRemoveQueueItemModalClose = () => {
this.props.onQueueRowModalOpenOrClose(false);
this.setState({ isRemoveQueueItemModalOpen: false }); this.setState({ isRemoveQueueItemModalOpen: false });
}; }
onInteractiveImportPress = () => { onInteractiveImportPress = () => {
this.props.onQueueRowModalOpenOrClose(true);
this.setState({ isInteractiveImportModalOpen: true }); this.setState({ isInteractiveImportModalOpen: true });
}; }
onInteractiveImportModalClose = () => { onInteractiveImportModalClose = () => {
this.props.onQueueRowModalOpenOrClose(false);
this.setState({ isInteractiveImportModalOpen: false }); this.setState({ isInteractiveImportModalOpen: false });
}; }
// //
// Render // Render
@@ -332,7 +319,6 @@ class QueueRow extends Component {
isOpen={isRemoveQueueItemModalOpen} isOpen={isRemoveQueueItemModalOpen}
sourceTitle={title} sourceTitle={title}
canIgnore={!!movie} canIgnore={!!movie}
isPending={isPending}
onRemovePress={this.onRemoveQueueItemModalConfirmed} onRemovePress={this.onRemoveQueueItemModalConfirmed}
onModalClose={this.onRemoveQueueItemModalClose} onModalClose={this.onRemoveQueueItemModalClose}
/> />
@@ -373,8 +359,7 @@ QueueRow.propTypes = {
columns: PropTypes.arrayOf(PropTypes.object).isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired,
onSelectedChange: PropTypes.func.isRequired, onSelectedChange: PropTypes.func.isRequired,
onGrabPress: PropTypes.func.isRequired, onGrabPress: PropTypes.func.isRequired,
onRemoveQueueItemPress: PropTypes.func.isRequired, onRemoveQueueItemPress: PropTypes.func.isRequired
onQueueRowModalOpenOrClose: PropTypes.func.isRequired
}; };
QueueRow.defaultProps = { QueueRow.defaultProps = {
@@ -37,11 +37,11 @@ class QueueRowConnector extends Component {
onGrabPress = () => { onGrabPress = () => {
this.props.grabQueueItem({ id: this.props.id }); this.props.grabQueueItem({ id: this.props.id });
}; }
onRemoveQueueItemPress = (payload) => { onRemoveQueueItemPress = (payload) => {
this.props.removeQueueItem({ id: this.props.id, ...payload }); this.props.removeQueueItem({ id: this.props.id, ...payload });
}; }
// //
// Render // Render
@@ -22,7 +22,7 @@ class RemoveQueueItemModal extends Component {
this.state = { this.state = {
remove: true, remove: true,
blocklist: false blacklist: false
}; };
} }
@@ -32,32 +32,32 @@ class RemoveQueueItemModal extends Component {
resetState = function() { resetState = function() {
this.setState({ this.setState({
remove: true, remove: true,
blocklist: false blacklist: false
}); });
}; }
// //
// Listeners // Listeners
onRemoveChange = ({ value }) => { onRemoveChange = ({ value }) => {
this.setState({ remove: value }); this.setState({ remove: value });
}; }
onBlocklistChange = ({ value }) => { onBlacklistChange = ({ value }) => {
this.setState({ blocklist: value }); this.setState({ blacklist: value });
}; }
onRemoveConfirmed = () => { onRemoveConfirmed = () => {
const state = this.state; const state = this.state;
this.resetState(); this.resetState();
this.props.onRemovePress(state); this.props.onRemovePress(state);
}; }
onModalClose = () => { onModalClose = () => {
this.resetState(); this.resetState();
this.props.onModalClose(); this.props.onModalClose();
}; }
// //
// Render // Render
@@ -66,11 +66,10 @@ class RemoveQueueItemModal extends Component {
const { const {
isOpen, isOpen,
sourceTitle, sourceTitle,
canIgnore, canIgnore
isPending
} = this.props; } = this.props;
const { remove, blocklist } = this.state; const { remove, blacklist } = this.state;
return ( return (
<Modal <Modal
@@ -90,31 +89,27 @@ class RemoveQueueItemModal extends Component {
{translate('RemoveFromQueueText', [sourceTitle])} {translate('RemoveFromQueueText', [sourceTitle])}
</div> </div>
{
isPending ?
null :
<FormGroup>
<FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="remove"
value={remove}
helpTextWarning={translate('RemoveHelpTextWarning')}
isDisabled={!canIgnore}
onChange={this.onRemoveChange}
/>
</FormGroup>
}
<FormGroup> <FormGroup>
<FormLabel>{translate('BlocklistRelease')}</FormLabel> <FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="blocklist" name="remove"
value={blocklist} value={remove}
helpText={translate('BlocklistHelpText')} helpTextWarning={translate('RemoveHelpTextWarning')}
onChange={this.onBlocklistChange} isDisabled={!canIgnore}
onChange={this.onRemoveChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('BlacklistRelease')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="blacklist"
value={blacklist}
helpText={translate('BlacklistHelpText')}
onChange={this.onBlacklistChange}
/> />
</FormGroup> </FormGroup>
@@ -142,7 +137,6 @@ RemoveQueueItemModal.propTypes = {
isOpen: PropTypes.bool.isRequired, isOpen: PropTypes.bool.isRequired,
sourceTitle: PropTypes.string.isRequired, sourceTitle: PropTypes.string.isRequired,
canIgnore: PropTypes.bool.isRequired, canIgnore: PropTypes.bool.isRequired,
isPending: PropTypes.bool.isRequired,
onRemovePress: PropTypes.func.isRequired, onRemovePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired
}; };
@@ -23,42 +23,42 @@ class RemoveQueueItemsModal extends Component {
this.state = { this.state = {
remove: true, remove: true,
blocklist: false blacklist: false
}; };
} }
// //
// Control // Control
resetState = function() { resetState = function() {
this.setState({ this.setState({
remove: true, remove: true,
blocklist: false blacklist: false
}); });
}; }
// //
// Listeners // Listeners
onRemoveChange = ({ value }) => { onRemoveChange = ({ value }) => {
this.setState({ remove: value }); this.setState({ remove: value });
}; }
onBlocklistChange = ({ value }) => { onBlacklistChange = ({ value }) => {
this.setState({ blocklist: value }); this.setState({ blacklist: value });
}; }
onRemoveConfirmed = () => { onRemoveConfirmed = () => {
const state = this.state; const state = this.state;
this.resetState(); this.resetState();
this.props.onRemovePress(state); this.props.onRemovePress(state);
}; }
onModalClose = () => { onModalClose = () => {
this.resetState(); this.resetState();
this.props.onModalClose(); this.props.onModalClose();
}; }
// //
// Render // Render
@@ -67,11 +67,10 @@ class RemoveQueueItemsModal extends Component {
const { const {
isOpen, isOpen,
selectedCount, selectedCount,
canIgnore, canIgnore
allPending
} = this.props; } = this.props;
const { remove, blocklist } = this.state; const { remove, blacklist } = this.state;
return ( return (
<Modal <Modal
@@ -83,42 +82,38 @@ class RemoveQueueItemsModal extends Component {
onModalClose={this.onModalClose} onModalClose={this.onModalClose}
> >
<ModalHeader> <ModalHeader>
{selectedCount > 1 ? translate('RemoveSelectedItems') : translate('RemoveSelectedItem')} Remove Selected Item{selectedCount > 1 ? 's' : ''}
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
<div className={styles.message}> <div className={styles.message}>
{selectedCount > 1 ? translate('AreYouSureYouWantToRemoveSelectedItemsFromQueue', selectedCount) : translate('AreYouSureYouWantToRemoveSelectedItemFromQueue')} {translate('AreYouSureYouWantToRemoveSelectedItemsFromQueue', [selectedCount, selectedCount > 1 ? 's' : ''])}
</div> </div>
{ <FormGroup>
allPending ? <FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
null :
<FormGroup>
<FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="remove" name="remove"
value={remove} value={remove}
helpTextWarning={translate('RemoveHelpTextWarning')} helpTextWarning={translate('RemoveHelpTextWarning')}
isDisabled={!canIgnore} isDisabled={!canIgnore}
onChange={this.onRemoveChange} onChange={this.onRemoveChange}
/> />
</FormGroup> </FormGroup>
}
<FormGroup> <FormGroup>
<FormLabel> <FormLabel>
{selectedCount > 1 ? translate('BlocklistReleases') : translate('BlocklistRelease')} Blacklist Release{selectedCount > 1 ? 's' : ''}
</FormLabel> </FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="blocklist" name="blacklist"
value={blocklist} value={blacklist}
helpText={translate('BlocklistHelpText')} helpText={translate('BlacklistHelpText')}
onChange={this.onBlocklistChange} onChange={this.onBlacklistChange}
/> />
</FormGroup> </FormGroup>
@@ -146,7 +141,6 @@ RemoveQueueItemsModal.propTypes = {
isOpen: PropTypes.bool.isRequired, isOpen: PropTypes.bool.isRequired,
selectedCount: PropTypes.number.isRequired, selectedCount: PropTypes.number.isRequired,
canIgnore: PropTypes.bool.isRequired, canIgnore: PropTypes.bool.isRequired,
allPending: PropTypes.bool.isRequired,
onRemovePress: PropTypes.func.isRequired, onRemovePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired
}; };
@@ -6,12 +6,12 @@
.searchIconContainer { .searchIconContainer {
width: 58px; width: 58px;
height: 46px; height: 46px;
border: 1px solid var(--inputBorderColor); border: 1px solid $inputBorderColor;
border-right: none; border-right: none;
border-radius: 4px; border-radius: 4px;
border-top-right-radius: 0; border-top-right-radius: 0;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
background-color: var(--searchIconContainerBackgroundColor); background-color: #edf1f2;
text-align: center; text-align: center;
line-height: 46px; line-height: 46px;
} }
@@ -25,7 +25,7 @@
} }
.clearLookupButton { .clearLookupButton {
border: 1px solid var(--inputBorderColor); border: 1px solid $inputBorderColor;
border-left: none; border-left: none;
border-top-right-radius: 4px; border-top-right-radius: 4px;
border-bottom-right-radius: 4px; border-bottom-right-radius: 4px;
@@ -67,12 +67,12 @@ class AddNewMovie extends Component {
this.props.onClearMovieLookup(); this.props.onClearMovieLookup();
} }
}); });
}; }
onClearMovieLookupPress = () => { onClearMovieLookupPress = () => {
this.setState({ term: '' }); this.setState({ term: '' });
this.props.onClearMovieLookup(); this.props.onClearMovieLookup();
}; }
// //
// Render // Render
@@ -81,8 +81,7 @@ class AddNewMovie extends Component {
const { const {
error, error,
items, items,
hasExistingMovies, hasExistingMovies
colorImpairedMode
} = this.props; } = this.props;
const term = this.state.term; const term = this.state.term;
@@ -103,7 +102,7 @@ class AddNewMovie extends Component {
className={styles.searchInput} className={styles.searchInput}
name="movieLookup" name="movieLookup"
value={term} value={term}
placeholder="e.g. The Dark Knight, tmdb:155, imdb:tt0468569" placeholder="eg. The Dark Knight, tmdb:155, imdb:tt0468569"
autoFocus={true} autoFocus={true}
onChange={this.onSearchInputChange} onChange={this.onSearchInputChange}
/> />
@@ -142,7 +141,6 @@ class AddNewMovie extends Component {
return ( return (
<AddNewMovieSearchResultConnector <AddNewMovieSearchResultConnector
key={item.tmdbId} key={item.tmdbId}
colorImpairedMode={colorImpairedMode}
{...item} {...item}
/> />
); );
@@ -161,7 +159,7 @@ class AddNewMovie extends Component {
{translate('YouCanAlsoSearch')} {translate('YouCanAlsoSearch')}
</div> </div>
<div> <div>
<Link to="https://wiki.servarr.com/radarr/faq#why-can-i-not-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>
@@ -215,8 +213,7 @@ AddNewMovie.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
hasExistingMovies: PropTypes.bool.isRequired, hasExistingMovies: PropTypes.bool.isRequired,
onMovieLookupChange: PropTypes.func.isRequired, onMovieLookupChange: PropTypes.func.isRequired,
onClearMovieLookup: PropTypes.func.isRequired, onClearMovieLookup: PropTypes.func.isRequired
colorImpairedMode: PropTypes.bool.isRequired
}; };
export default AddNewMovie; export default AddNewMovie;
@@ -3,10 +3,8 @@ import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { clearAddMovie, lookupMovie } from 'Store/Actions/addMovieActions'; import { clearAddMovie, lookupMovie } from 'Store/Actions/addMovieActions';
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
import { fetchRootFolders } from 'Store/Actions/rootFolderActions'; import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
import { fetchImportExclusions } from 'Store/Actions/Settings/importExclusions'; import { fetchImportExclusions } from 'Store/Actions/Settings/importExclusions';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import parseUrl from 'Utilities/String/parseUrl'; import parseUrl from 'Utilities/String/parseUrl';
import AddNewMovie from './AddNewMovie'; import AddNewMovie from './AddNewMovie';
@@ -15,15 +13,13 @@ function createMapStateToProps() {
(state) => state.addMovie, (state) => state.addMovie,
(state) => state.movies.items.length, (state) => state.movies.items.length,
(state) => state.router.location, (state) => state.router.location,
createUISettingsSelector(), (addMovie, existingMoviesCount, location) => {
(addMovie, existingMoviesCount, location, uiSettings) => {
const { params } = parseUrl(location.search); const { params } = parseUrl(location.search);
return { return {
...addMovie, ...addMovie,
term: params.term, term: params.term,
hasExistingMovies: existingMoviesCount > 0, hasExistingMovies: existingMoviesCount > 0
colorImpairedMode: uiSettings.enableColorImpairedMode
}; };
} }
); );
@@ -33,9 +29,7 @@ const mapDispatchToProps = {
lookupMovie, lookupMovie,
clearAddMovie, clearAddMovie,
fetchRootFolders, fetchRootFolders,
fetchImportExclusions, fetchImportExclusions
fetchQueueDetails,
clearQueueDetails
}; };
class AddNewMovieConnector extends Component { class AddNewMovieConnector extends Component {
@@ -52,7 +46,6 @@ class AddNewMovieConnector extends Component {
componentDidMount() { componentDidMount() {
this.props.fetchRootFolders(); this.props.fetchRootFolders();
this.props.fetchImportExclusions(); this.props.fetchImportExclusions();
this.props.fetchQueueDetails();
} }
componentWillUnmount() { componentWillUnmount() {
@@ -61,7 +54,6 @@ class AddNewMovieConnector extends Component {
} }
this.props.clearAddMovie(); this.props.clearAddMovie();
this.props.clearQueueDetails();
} }
// //
@@ -79,11 +71,11 @@ class AddNewMovieConnector extends Component {
this.props.lookupMovie({ term }); this.props.lookupMovie({ term });
}, 300); }, 300);
} }
}; }
onClearMovieLookup = () => { onClearMovieLookup = () => {
this.props.clearAddMovie(); this.props.clearAddMovie();
}; }
// //
// Render // Render
@@ -110,9 +102,7 @@ AddNewMovieConnector.propTypes = {
lookupMovie: PropTypes.func.isRequired, lookupMovie: PropTypes.func.isRequired,
clearAddMovie: PropTypes.func.isRequired, clearAddMovie: PropTypes.func.isRequired,
fetchRootFolders: PropTypes.func.isRequired, fetchRootFolders: PropTypes.func.isRequired,
fetchImportExclusions: PropTypes.func.isRequired, fetchImportExclusions: PropTypes.func.isRequired
fetchQueueDetails: PropTypes.func.isRequired,
clearQueueDetails: PropTypes.func.isRequired
}; };
export default connect(createMapStateToProps, mapDispatchToProps)(AddNewMovieConnector); export default connect(createMapStateToProps, mapDispatchToProps)(AddNewMovieConnector);
@@ -4,7 +4,7 @@
.year { .year {
margin-left: 5px; margin-left: 5px;
color: var(--disabledColor); color: $disabledColor;
} }
.poster { .poster {
@@ -22,11 +22,11 @@ class AddNewMovieModalContent extends Component {
onQualityProfileIdChange = ({ value }) => { onQualityProfileIdChange = ({ value }) => {
this.props.onInputChange({ name: 'qualityProfileId', value: parseInt(value) }); this.props.onInputChange({ name: 'qualityProfileId', value: parseInt(value) });
}; }
onAddMoviePress = () => { onAddMoviePress = () => {
this.props.onAddMoviePress(); this.props.onAddMoviePress();
}; }
// //
// Render // Render
@@ -51,7 +51,7 @@ class AddNewMovieModalContentConnector extends Component {
onInputChange = ({ name, value }) => { onInputChange = ({ name, value }) => {
this.props.setAddMovieDefault({ [name]: value }); this.props.setAddMovieDefault({ [name]: value });
}; }
onAddMoviePress = () => { onAddMoviePress = () => {
const { const {
@@ -73,7 +73,7 @@ class AddNewMovieModalContentConnector extends Component {
searchForMovie: searchForMovie.value, searchForMovie: searchForMovie.value,
tags: tags.value tags: tags.value
}); });
}; }
// //
// Render // Render
@@ -9,15 +9,13 @@
.underlay { .underlay {
@add-mixin cover; @add-mixin cover;
background-color: var(--addMovieBackgroundColor); background-color: $white;
transition: background 500ms; transition: background 500ms;
&:hover { &:hover {
background-color: var(--pageBackground); background-color: #eaf2ff;
box-shadow: 0 0 12px var(--black);
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;
transition: all 200ms ease-in;
} }
} }
@@ -29,11 +27,9 @@
} }
.poster { .poster {
position: relative; flex: 0 0 170px;
display: block;
margin-right: 20px; margin-right: 20px;
height: 250px; height: 250px;
background-color: var(--defaultColor);
} }
.content { .content {
@@ -58,7 +54,7 @@
.year { .year {
margin-left: 10px; margin-left: 10px;
color: var(--disabledColor); color: $disabledColor;
} }
.icons { .icons {
@@ -77,7 +73,7 @@
.exclusionIcon { .exclusionIcon {
margin-left: 10px; margin-left: 10px;
color: var(--dangerColor); color: $dangerColor;
pointer-events: all; pointer-events: all;
} }
@@ -90,28 +86,6 @@
pointer-events: all; pointer-events: all;
} }
.posterContainer {
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 {
margin-right: 22px;
font-weight: bold;
}
@media only screen and (max-width: $breakpointMedium) { @media only screen and (max-width: $breakpointMedium) {
.titleRow { .titleRow {
justify-content: space-between; justify-content: space-between;
@@ -1,16 +1,13 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import HeartRating from 'Components/HeartRating';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import Label from 'Components/Label'; import Label from 'Components/Label';
import Link from 'Components/Link/Link'; import Link from 'Components/Link/Link';
import TmdbRating from 'Components/TmdbRating';
import Tooltip from 'Components/Tooltip/Tooltip'; import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props'; import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks'; import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
import MovieStatusLabel from 'Movie/Details/MovieStatusLabel';
import MovieIndexProgressBar from 'Movie/Index/ProgressBar/MovieIndexProgressBar';
import MoviePoster from 'Movie/MoviePoster'; import MoviePoster from 'Movie/MoviePoster';
import formatRuntime from 'Utilities/Date/formatRuntime';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import AddNewMovieModal from './AddNewMovieModal'; import AddNewMovieModal from './AddNewMovieModal';
import styles from './AddNewMovieSearchResult.css'; import styles from './AddNewMovieSearchResult.css';
@@ -39,15 +36,15 @@ class AddNewMovieSearchResult extends Component {
onPress = () => { onPress = () => {
this.setState({ isNewAddMovieModalOpen: true }); this.setState({ isNewAddMovieModalOpen: true });
}; }
onAddMovieModalClose = () => { onAddMovieModalClose = () => {
this.setState({ isNewAddMovieModalOpen: false }); this.setState({ isNewAddMovieModalOpen: false });
}; }
onExternalLinkPress = (event) => { onExternalLinkPress = (event) => {
event.stopPropagation(); event.stopPropagation();
}; }
// //
// Render // Render
@@ -68,17 +65,7 @@ class AddNewMovieSearchResult extends Component {
images, images,
isExistingMovie, isExistingMovie,
isExclusionMovie, isExclusionMovie,
isSmallScreen, isSmallScreen
colorImpairedMode,
id,
monitored,
hasFile,
isAvailable,
queueStatus,
queueState,
runtime,
movieRuntimeFormat,
certification
} = this.props; } = this.props;
const { const {
@@ -86,13 +73,6 @@ class AddNewMovieSearchResult extends Component {
} = this.state; } = this.state;
const linkProps = isExistingMovie ? { to: `/movie/${titleSlug}` } : { onPress: this.onPress }; const linkProps = isExistingMovie ? { to: `/movie/${titleSlug}` } : { onPress: this.onPress };
const posterWidth = 167;
const posterHeight = 250;
const elementStyle = {
width: `${posterWidth}px`,
height: `${posterHeight}px`
};
return ( return (
<div className={styles.searchResult}> <div className={styles.searchResult}>
@@ -105,31 +85,12 @@ class AddNewMovieSearchResult extends Component {
{ {
isSmallScreen ? isSmallScreen ?
null : null :
<div> <MoviePoster
<div className={styles.posterContainer}> className={styles.poster}
<MoviePoster images={images}
className={styles.poster} size={250}
style={elementStyle} overflow={true}
images={images} />
size={250}
overflow={true}
/>
</div>
{
isExistingMovie &&
<MovieIndexProgressBar
monitored={monitored}
hasFile={hasFile}
status={status}
posterWidth={posterWidth}
detailedProgressBar={true}
queueStatus={queueStatus}
queueState={queueState}
isAvailable={isAvailable}
/>
}
</div>
} }
<div className={styles.content}> <div className={styles.content}>
@@ -172,26 +133,10 @@ class AddNewMovieSearchResult extends Component {
</div> </div>
</div> </div>
<div>
{
!!certification &&
<span className={styles.certification}>
{certification}
</span>
}
{
!!runtime &&
<span className={styles.runtime}>
{formatRuntime(runtime, movieRuntimeFormat)}
</span>
}
</div>
<div> <div>
<Label size={sizes.LARGE}> <Label size={sizes.LARGE}>
<TmdbRating <HeartRating
ratings={ratings} rating={ratings.value}
iconSize={13} iconSize={13}
/> />
</Label> </Label>
@@ -231,15 +176,13 @@ class AddNewMovieSearchResult extends Component {
/> />
{ {
isExistingMovie && isSmallScreen && status === 'ended' &&
<MovieStatusLabel <Label
hasMovieFiles={hasFile} kind={kinds.DANGER}
monitored={monitored} size={sizes.LARGE}
isAvailable={isAvailable} >
id={id} Ended
useLabel={true} </Label>
colorImpairedMode={colorImpairedMode}
/>
} }
</div> </div>
@@ -279,18 +222,7 @@ AddNewMovieSearchResult.propTypes = {
images: PropTypes.arrayOf(PropTypes.object).isRequired, images: PropTypes.arrayOf(PropTypes.object).isRequired,
isExistingMovie: PropTypes.bool.isRequired, isExistingMovie: PropTypes.bool.isRequired,
isExclusionMovie: PropTypes.bool.isRequired, isExclusionMovie: PropTypes.bool.isRequired,
isSmallScreen: PropTypes.bool.isRequired, isSmallScreen: PropTypes.bool.isRequired
id: PropTypes.number,
queueItems: PropTypes.arrayOf(PropTypes.object),
monitored: PropTypes.bool.isRequired,
hasFile: PropTypes.bool.isRequired,
isAvailable: PropTypes.bool.isRequired,
colorImpairedMode: PropTypes.bool,
queueStatus: PropTypes.string,
queueState: PropTypes.string,
runtime: PropTypes.number.isRequired,
movieRuntimeFormat: PropTypes.string.isRequired,
certification: PropTypes.string
}; };
export default AddNewMovieSearchResult; export default AddNewMovieSearchResult;
@@ -10,17 +10,11 @@ function createMapStateToProps() {
createExistingMovieSelector(), createExistingMovieSelector(),
createExclusionMovieSelector(), createExclusionMovieSelector(),
createDimensionsSelector(), createDimensionsSelector(),
(state) => state.queue.details.items, (isExistingMovie, isExclusionMovie, dimensions) => {
(state, { internalId }) => internalId,
(isExistingMovie, isExclusionMovie, dimensions, queueItems, internalId) => {
const firstQueueItem = queueItems.find((q) => q.movieId === internalId && internalId > 0);
return { return {
isExistingMovie, isExistingMovie,
isExclusionMovie, isExclusionMovie,
isSmallScreen: dimensions.isSmallScreen, isSmallScreen: dimensions.isSmallScreen
queueStatus: firstQueueItem ? firstQueueItem.status : null,
queueState: firstQueueItem ? firstQueueItem.trackedDownloadState : null
}; };
} }
); );
@@ -32,25 +32,25 @@ class ImportMovie extends Component {
setScrollerRef = (ref) => { setScrollerRef = (ref) => {
this.setState({ scroller: ref }); this.setState({ scroller: ref });
}; }
// //
// Listeners // Listeners
getSelectedIds = () => { getSelectedIds = () => {
return getSelectedIds(this.state.selectedState, { parseIds: false }); return getSelectedIds(this.state.selectedState, { parseIds: false });
}; }
onSelectAllChange = ({ value }) => { onSelectAllChange = ({ value }) => {
// Only select non-dupes // Only select non-dupes
this.setState(selectAll(this.state.selectedState, value)); this.setState(selectAll(this.state.selectedState, value));
}; }
onSelectedChange = ({ id, value, shiftKey = false }) => { onSelectedChange = ({ id, value, shiftKey = false }) => {
this.setState((state) => { this.setState((state) => {
return toggleSelected(state, this.props.items, id, value, shiftKey); return toggleSelected(state, this.props.items, id, value, shiftKey);
}); });
}; }
onRemoveSelectedStateItem = (id) => { onRemoveSelectedStateItem = (id) => {
this.setState((state) => { this.setState((state) => {
@@ -62,15 +62,15 @@ class ImportMovie extends Component {
selectedState selectedState
}; };
}); });
}; }
onInputChange = ({ name, value }) => { onInputChange = ({ name, value }) => {
this.props.onInputChange(this.getSelectedIds(), name, value); this.props.onInputChange(this.getSelectedIds(), name, value);
}; }
onImportPress = () => { onImportPress = () => {
this.props.onImportPress(this.getSelectedIds()); this.props.onImportPress(this.getSelectedIds());
}; }
// //
// Render // Render
@@ -112,11 +112,11 @@ class ImportMovieConnector extends Component {
[name]: value [name]: value
}); });
}); });
}; }
onImportPress = (ids) => { onImportPress = (ids) => {
this.props.dispatchImportMovie({ ids }); this.props.dispatchImportMovie({ ids });
}; }
// //
// Render // Render
@@ -83,7 +83,7 @@ class ImportMovieFooter extends Component {
onInputChange = ({ name, value }) => { onInputChange = ({ name, value }) => {
this.setState({ [name]: value }); this.setState({ [name]: value });
this.props.onInputChange({ name, value }); this.props.onInputChange({ name, value });
}; }
// //
// Render // Render
@@ -225,19 +225,13 @@ class ImportMovieFooter extends Component {
body={ body={
<ul> <ul>
{ {
Array.isArray(importError.responseJSON) ? importError.responseJSON.map((error, index) => {
importError.responseJSON.map((error, index) => { return (
return ( <li key={index}>
<li key={index}> {error.errorMessage}
{error.errorMessage} </li>
</li> );
); })
}) :
<li>
{
JSON.stringify(importError.responseJSON)
}
</li>
} }
</ul> </ul>
} }
@@ -48,7 +48,7 @@ class ImportMovieRowConnector extends Component {
id: this.props.id, id: this.props.id,
[name]: value [name]: value
}); });
}; }
// //
// Render // Render
@@ -121,7 +121,7 @@ class ImportMovieTable extends Component {
/> />
</VirtualTableRow> </VirtualTableRow>
); );
}; }
// //
// Render // Render
@@ -1,25 +1,8 @@
.container { .movie {
display: flex;
padding: 10px 20px; padding: 10px 20px;
width: 100%; width: 100%;
&:hover { &:hover {
background-color: var(--menuItemHoverBackgroundColor); background-color: $menuItemHoverBackgroundColor;
} }
} }
.movie {
flex: 1 0 0;
overflow: hidden;
}
.tmdbLink {
composes: link from '~Components/Link/Link.css';
margin-left: auto;
color: var(--textColor);
}
.tmdbLinkIcon {
margin-left: 10px;
}
@@ -1,8 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link'; import Link from 'Components/Link/Link';
import { icons } from 'Helpers/Props';
import ImportMovieTitle from './ImportMovieTitle'; import ImportMovieTitle from './ImportMovieTitle';
import styles from './ImportMovieSearchResult.css'; import styles from './ImportMovieSearchResult.css';
@@ -13,14 +11,13 @@ class ImportMovieSearchResult extends Component {
onPress = () => { onPress = () => {
this.props.onPress(this.props.tmdbId); this.props.onPress(this.props.tmdbId);
}; }
// //
// Render // Render
render() { render() {
const { const {
tmdbId,
title, title,
year, year,
studio, studio,
@@ -28,30 +25,17 @@ class ImportMovieSearchResult extends Component {
} = this.props; } = this.props;
return ( return (
<div className={styles.container}> <Link
<Link className={styles.movie}
className={styles.movie} onPress={this.onPress}
onPress={this.onPress} >
> <ImportMovieTitle
<ImportMovieTitle title={title}
title={title} year={year}
year={year} network={studio}
network={studio} isExistingMovie={isExistingMovie}
isExistingMovie={isExistingMovie} />
/> </Link>
</Link>
<Link
className={styles.tmdbLink}
to={`https://www.themoviedb.org/movie/${tmdbId}`}
>
<Icon
className={styles.tmdbLinkIcon}
name={icons.EXTERNAL_LINK}
size={16}
/>
</Link>
</div>
); );
} }
} }
@@ -7,10 +7,10 @@
padding: 6px 16px; padding: 6px 16px;
width: 100%; width: 100%;
height: 35px; height: 35px;
border: 1px solid var(--inputBorderColor); border: 1px solid $inputBorderColor;
border-radius: 4px; border-radius: 4px;
background-color: var(--inputBackgroundColor); background-color: $white;
box-shadow: inset 0 1px 1px var(--inputBoxShadowColor); box-shadow: inset 0 1px 1px $inputBoxShadowColor;
} }
.loading { .loading {
@@ -38,9 +38,9 @@
.content { .content {
padding: 4px; padding: 4px;
border: 1px solid var(--inputBorderColor); border: 1px solid $inputBorderColor;
border-radius: 4px; border-radius: 4px;
background-color: var(--inputBackgroundColor); background-color: $white;
} }
.searchContainer { .searchContainer {
@@ -49,12 +49,12 @@
.searchIconContainer { .searchIconContainer {
width: 58px; width: 58px;
border: 1px solid var(--inputBorderColor); border: 1px solid $inputBorderColor;
border-right: none; border-right: none;
border-radius: 4px; border-radius: 4px;
border-top-right-radius: 0; border-top-right-radius: 0;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
background-color: var(--searchIconContainerBackgroundColor); background-color: #edf1f2;
text-align: center; text-align: center;
line-height: 33px; line-height: 33px;
} }
@@ -69,7 +69,7 @@ class ImportMovieSelectMovie extends Component {
this.setState({ isOpen: false }); this.setState({ isOpen: false });
this._removeListener(); this._removeListener();
} }
}; }
onPress = () => { onPress = () => {
if (this.state.isOpen) { if (this.state.isOpen) {
@@ -79,7 +79,7 @@ class ImportMovieSelectMovie extends Component {
} }
this.setState({ isOpen: !this.state.isOpen }); this.setState({ isOpen: !this.state.isOpen });
}; }
onSearchInputChange = ({ value }) => { onSearchInputChange = ({ value }) => {
if (this._movieLookupTimeout) { if (this._movieLookupTimeout) {
@@ -91,17 +91,17 @@ class ImportMovieSelectMovie extends Component {
this.props.onSearchInputChange(value); this.props.onSearchInputChange(value);
}, 200); }, 200);
}); });
}; }
onRefreshPress = () => { onRefreshPress = () => {
this.props.onSearchInputChange(this.state.term); this.props.onSearchInputChange(this.state.term);
}; }
onMovieSelect = (tmdbId) => { onMovieSelect = (tmdbId) => {
this.setState({ isOpen: false }); this.setState({ isOpen: false });
this.props.onMovieSelect(tmdbId); this.props.onMovieSelect(tmdbId);
}; }
// //
// Render // Render
@@ -36,7 +36,7 @@ class ImportMovieSelectMovieConnector extends Component {
term, term,
topOfQueue: true topOfQueue: true
}); });
}; }
onMovieSelect = (tmdbId) => { onMovieSelect = (tmdbId) => {
const { const {
@@ -48,7 +48,7 @@ class ImportMovieSelectMovieConnector extends Component {
id, id,
selectedMovie: _.find(items, { tmdbId }) selectedMovie: _.find(items, { tmdbId })
}); });
}; }
// //
// Render // Render
@@ -9,7 +9,7 @@
.year { .year {
margin-left: 5px; margin-left: 5px;
color: var(--disabledColor); color: $disabledColor;
} }
.existing { .existing {
@@ -25,7 +25,7 @@ class ImportMovieRootFolderRowConnector extends Component {
onDeletePress = () => { onDeletePress = () => {
this.props.deleteRootFolder({ id: this.props.id }); this.props.deleteRootFolder({ id: this.props.id });
}; }
// //
// Render // Render
@@ -30,9 +30,3 @@
.importButtonIcon { .importButtonIcon {
margin-right: 8px; margin-right: 8px;
} }
.addErrorAlert {
composes: alert from '~Components/Alert.css';
margin: 20px 0;
}
@@ -1,6 +1,5 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import 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';
@@ -55,15 +54,15 @@ class ImportMovieSelectFolder extends Component {
onAddNewRootFolderPress = () => { onAddNewRootFolderPress = () => {
this.setState({ isAddNewRootFolderModalOpen: true }); this.setState({ isAddNewRootFolderModalOpen: true });
}; }
onNewRootFolderSelect = ({ value }) => { onNewRootFolderSelect = ({ value }) => {
this.props.onNewRootFolderSelect(value); this.props.onNewRootFolderSelect(value);
}; }
onAddRootFolderModalClose = () => { onAddRootFolderModalClose = () => {
this.setState({ isAddNewRootFolderModalOpen: false }); this.setState({ isAddNewRootFolderModalOpen: false });
}; }
// //
// Render // Render
@@ -73,29 +72,23 @@ 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
} }
{ {
@@ -115,7 +108,7 @@ class ImportMovieSelectFolder extends Component {
</div> </div>
{ {
hasRootFolders ? items.length > 0 ?
<div className={styles.recentFolders}> <div className={styles.recentFolders}>
<FieldSet legend={translate('RecentFolders')}> <FieldSet legend={translate('RecentFolders')}>
<Table <Table
@@ -138,57 +131,35 @@ 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>
{
Array.isArray(saveError.responseJSON) ?
saveError.responseJSON.map((e, index) => {
return (
<li key={index}>
{e.errorMessage}
</li>
);
}) :
<li>
{
JSON.stringify(saveError.responseJSON)
}
</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"
@@ -208,9 +179,7 @@ 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
@@ -58,11 +58,11 @@ class ImportMovieSelectFolderConnector extends Component {
onNewRootFolderSelect = (path) => { onNewRootFolderSelect = (path) => {
this.props.addRootFolder({ path }); this.props.addRootFolder({ path });
}; }
onDeleteRootFolderPress = (id) => { onDeleteRootFolderPress = (id) => {
this.props.deleteRootFolder({ id }); this.props.deleteRootFolder({ id });
}; }
// //
// Render // Render
+4 -7
View File
@@ -4,19 +4,16 @@ import React from 'react';
import DocumentTitle from 'react-document-title'; import DocumentTitle from 'react-document-title';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import PageConnector from 'Components/Page/PageConnector'; import PageConnector from 'Components/Page/PageConnector';
import ApplyTheme from './ApplyTheme';
import AppRoutes from './AppRoutes'; import AppRoutes from './AppRoutes';
function App({ store, history }) { function App({ store, history }) {
return ( return (
<DocumentTitle title={window.Radarr.instanceName}> <DocumentTitle title="Radarr">
<Provider store={store}> <Provider store={store}>
<ConnectedRouter history={history}> <ConnectedRouter history={history}>
<ApplyTheme> <PageConnector>
<PageConnector> <AppRoutes app={App} />
<AppRoutes app={App} /> </PageConnector>
</PageConnector>
</ApplyTheme>
</ConnectedRouter> </ConnectedRouter>
</Provider> </Provider>
</DocumentTitle> </DocumentTitle>
+5 -11
View File
@@ -1,13 +1,12 @@
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 BlocklistConnector from 'Activity/Blocklist/BlocklistConnector'; import BlacklistConnector from 'Activity/Blacklist/BlacklistConnector';
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';
import ImportMovies from 'AddMovie/ImportMovie/ImportMovies'; import ImportMovies from 'AddMovie/ImportMovie/ImportMovies';
import CalendarPageConnector from 'Calendar/CalendarPageConnector'; import CalendarPageConnector from 'Calendar/CalendarPageConnector';
import CollectionConnector from 'Collection/CollectionConnector';
import NotFound from 'Components/NotFound'; import NotFound from 'Components/NotFound';
import Switch from 'Components/Router/Switch'; import Switch from 'Components/Router/Switch';
import DiscoverMovieConnector from 'DiscoverMovie/DiscoverMovieConnector'; import DiscoverMovieConnector from 'DiscoverMovie/DiscoverMovieConnector';
@@ -22,7 +21,7 @@ import MediaManagementConnector from 'Settings/MediaManagement/MediaManagementCo
import MetadataSettings from 'Settings/Metadata/MetadataSettings'; import MetadataSettings from 'Settings/Metadata/MetadataSettings';
import NotificationSettings from 'Settings/Notifications/NotificationSettings'; import NotificationSettings from 'Settings/Notifications/NotificationSettings';
import Profiles from 'Settings/Profiles/Profiles'; import Profiles from 'Settings/Profiles/Profiles';
import QualityConnector from 'Settings/Quality/QualityConnector'; import Quality from 'Settings/Quality/Quality';
import Settings from 'Settings/Settings'; import Settings from 'Settings/Settings';
import TagSettings from 'Settings/Tags/TagSettings'; import TagSettings from 'Settings/Tags/TagSettings';
import UISettingsConnector from 'Settings/UI/UISettingsConnector'; import UISettingsConnector from 'Settings/UI/UISettingsConnector';
@@ -73,11 +72,6 @@ function AppRoutes(props) {
component={AddNewMovieConnector} component={AddNewMovieConnector}
/> />
<Route
path="/collections"
component={CollectionConnector}
/>
<Route <Route
path="/add/import" path="/add/import"
component={ImportMovies} component={ImportMovies}
@@ -117,8 +111,8 @@ function AppRoutes(props) {
/> />
<Route <Route
path="/activity/blocklist" path="/activity/blacklist"
component={BlocklistConnector} component={BlacklistConnector}
/> />
{/* {/*
@@ -143,7 +137,7 @@ function AppRoutes(props) {
<Route <Route
path="/settings/quality" path="/settings/quality"
component={QualityConnector} component={Quality}
/> />
<Route <Route
+1 -40
View File
@@ -11,47 +11,9 @@ import UpdateChanges from 'System/Updates/UpdateChanges';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import styles from './AppUpdatedModalContent.css'; import styles from './AppUpdatedModalContent.css';
function mergeUpdates(items, version, prevVersion) {
let installedIndex = items.findIndex((u) => u.version === version);
let installedPreviouslyIndex = items.findIndex((u) => u.version === prevVersion);
if (installedIndex === -1) {
installedIndex = 0;
}
if (installedPreviouslyIndex === -1) {
installedPreviouslyIndex = items.length;
} else if (installedPreviouslyIndex === installedIndex && items.length) {
installedPreviouslyIndex += 1;
}
const appliedUpdates = items.slice(installedIndex, installedPreviouslyIndex);
if (!appliedUpdates.length) {
return null;
}
const appliedChanges = { new: [], fixed: [] };
appliedUpdates.forEach((u) => {
if (u.changes) {
appliedChanges.new.push(... u.changes.new);
appliedChanges.fixed.push(... u.changes.fixed);
}
});
const mergedUpdate = Object.assign({}, appliedUpdates[0], { changes: appliedChanges });
if (!appliedChanges.new.length && !appliedChanges.fixed.length) {
mergedUpdate.changes = null;
}
return mergedUpdate;
}
function AppUpdatedModalContent(props) { function AppUpdatedModalContent(props) {
const { const {
version, version,
prevVersion,
isPopulated, isPopulated,
error, error,
items, items,
@@ -59,7 +21,7 @@ function AppUpdatedModalContent(props) {
onModalClose onModalClose
} = props; } = props;
const update = mergeUpdates(items, version, prevVersion); const update = items[0];
return ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
@@ -127,7 +89,6 @@ function AppUpdatedModalContent(props) {
AppUpdatedModalContent.propTypes = { AppUpdatedModalContent.propTypes = {
version: PropTypes.string.isRequired, version: PropTypes.string.isRequired,
prevVersion: PropTypes.string,
isPopulated: PropTypes.bool.isRequired, isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object, error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
@@ -8,9 +8,8 @@ import AppUpdatedModalContent from './AppUpdatedModalContent';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
(state) => state.app.version, (state) => state.app.version,
(state) => state.app.prevVersion,
(state) => state.system.updates, (state) => state.system.updates,
(version, prevVersion, updates) => { (version, updates) => {
const { const {
isPopulated, isPopulated,
error, error,
@@ -19,7 +18,6 @@ function createMapStateToProps() {
return { return {
version, version,
prevVersion,
isPopulated, isPopulated,
error, error,
items items
-49
View File
@@ -1,49 +0,0 @@
import PropTypes from 'prop-types';
import React, { Fragment, useCallback, useEffect } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import themes from 'Styles/Themes';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.ui.item.theme || window.Radarr.theme,
(
theme
) => {
return {
theme
};
}
);
}
function ApplyTheme({ theme, children }) {
// Update the CSS Variables
const updateCSSVariables = useCallback(() => {
const arrayOfVariableKeys = Object.keys(themes[theme]);
const arrayOfVariableValues = Object.values(themes[theme]);
// Loop through each array key and set the CSS Variables
arrayOfVariableKeys.forEach((cssVariableKey, index) => {
// Based on our snippet from MDN
document.documentElement.style.setProperty(
`--${cssVariableKey}`,
arrayOfVariableValues[index]
);
});
}, [theme]);
// On Component Mount and Component Update
useEffect(() => {
updateCSSVariables(theme);
}, [updateCSSVariables, theme]);
return <Fragment>{children}</Fragment>;
}
ApplyTheme.propTypes = {
theme: PropTypes.string.isRequired,
children: PropTypes.object.isRequired
};
export default connect(createMapStateToProps)(ApplyTheme);
+9 -13
View File
@@ -2,11 +2,11 @@
display: flex; display: flex;
overflow-x: hidden; overflow-x: hidden;
padding: 5px; padding: 5px;
border-bottom: 1px solid var(--borderColor); border-bottom: 1px solid $borderColor;
font-size: $defaultFontSize; font-size: $defaultFontSize;
&:hover { &:hover {
background-color: var(--tableRowHoverBackgroundColor); background-color: $tableRowHoverBackgroundColor;
} }
} }
@@ -29,7 +29,7 @@
} }
.time { .time {
flex: 0 0 125px; flex: 0 0 120px;
margin-right: 10px; margin-right: 10px;
border: none !important; border: none !important;
} }
@@ -54,24 +54,20 @@
composes: downloaded from '~Calendar/Events/CalendarEvent.css'; composes: downloaded from '~Calendar/Events/CalendarEvent.css';
} }
.queue { .downloading {
composes: queue from '~Calendar/Events/CalendarEvent.css'; composes: downloading from '~Calendar/Events/CalendarEvent.css';
} }
.unmonitored { .unmonitored {
composes: unmonitored from '~Calendar/Events/CalendarEvent.css'; composes: unmonitored from '~Calendar/Events/CalendarEvent.css';
} }
.missingUnmonitored { .missing {
composes: missingUnmonitored from '~Calendar/Events/CalendarEvent.css'; composes: missing from '~Calendar/Events/CalendarEvent.css';
} }
.missingMonitored { .unreleased {
composes: missingMonitored from '~Calendar/Events/CalendarEvent.css'; composes: unreleased 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) {
+4 -4
View File
@@ -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';
@@ -27,11 +27,11 @@ class AgendaEvent extends Component {
onPress = () => { onPress = () => {
this.setState({ isDetailsModalOpen: true }); this.setState({ isDetailsModalOpen: true });
}; }
onDetailsModalClose = () => { onDetailsModalClose = () => {
this.setState({ isDetailsModalOpen: false }); this.setState({ isDetailsModalOpen: false });
}; }
// //
// Render // Render
@@ -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(null, isMonitored, hasFile, isAvailable, 'style', downloading); const statusStyle = getStatusStyle(hasFile, downloading, isAvailable, isMonitored);
const joinedGenres = genres.slice(0, 2).join(', '); const joinedGenres = genres.slice(0, 2).join(', ');
const link = `/movie/${titleSlug}`; const link = `/movie/${titleSlug}`;
+8 -8
View File
@@ -119,43 +119,43 @@ class CalendarConnector extends Component {
this.props.fetchQueueDetails({ time, view }); this.props.fetchQueueDetails({ time, view });
this.props.fetchCalendar({ time, view }); this.props.fetchCalendar({ time, view });
}; }
scheduleUpdate = () => { scheduleUpdate = () => {
this.clearUpdateTimeout(); this.clearUpdateTimeout();
this.updateTimeoutId = setTimeout(this.updateCalendar, UPDATE_DELAY); this.updateTimeoutId = setTimeout(this.updateCalendar, UPDATE_DELAY);
}; }
clearUpdateTimeout = () => { clearUpdateTimeout = () => {
if (this.updateTimeoutId) { if (this.updateTimeoutId) {
clearTimeout(this.updateTimeoutId); clearTimeout(this.updateTimeoutId);
} }
}; }
updateCalendar = () => { updateCalendar = () => {
this.props.gotoCalendarToday(); this.props.gotoCalendarToday();
this.scheduleUpdate(); this.scheduleUpdate();
}; }
// //
// Listeners // Listeners
onCalendarViewChange = (view) => { onCalendarViewChange = (view) => {
this.props.setCalendarView({ view }); this.props.setCalendarView({ view });
}; }
onTodayPress = () => { onTodayPress = () => {
this.props.gotoCalendarToday(); this.props.gotoCalendarToday();
}; }
onPreviousPress = () => { onPreviousPress = () => {
this.props.gotoCalendarPreviousRange(); this.props.gotoCalendarPreviousRange();
}; }
onNextPress = () => { onNextPress = () => {
this.props.gotoCalendarNextRange(); this.props.gotoCalendarNextRange();
}; }
// //
// Render // Render
+6 -6
View File
@@ -44,23 +44,23 @@ class CalendarPage extends Component {
const days = Math.max(3, Math.min(7, Math.floor(width / MINIMUM_DAY_WIDTH))); const days = Math.max(3, Math.min(7, Math.floor(width / MINIMUM_DAY_WIDTH)));
this.props.onDaysCountChange(days); this.props.onDaysCountChange(days);
}; }
onGetCalendarLinkPress = () => { onGetCalendarLinkPress = () => {
this.setState({ isCalendarLinkModalOpen: true }); this.setState({ isCalendarLinkModalOpen: true });
}; }
onGetCalendarLinkModalClose = () => { onGetCalendarLinkModalClose = () => {
this.setState({ isCalendarLinkModalOpen: false }); this.setState({ isCalendarLinkModalOpen: false });
}; }
onOptionsPress = () => { onOptionsPress = () => {
this.setState({ isOptionsModalOpen: true }); this.setState({ isOptionsModalOpen: true });
}; }
onOptionsModalClose = () => { onOptionsModalClose = () => {
this.setState({ isOptionsModalOpen: false }); this.setState({ isOptionsModalOpen: false });
}; }
onSearchMissingPress = () => { onSearchMissingPress = () => {
const { const {
@@ -69,7 +69,7 @@ class CalendarPage extends Component {
} = this.props; } = this.props;
onSearchMissingPress(missingMovieIds); onSearchMissingPress(missingMovieIds);
}; }
// //
// Render // Render
@@ -24,7 +24,7 @@ function createMissingMovieIdsSelector() {
const inCinemas = movie.inCinemas; const inCinemas = movie.inCinemas;
if ( if (
!movie.hasFile && !movie.movieFileId &&
moment(inCinemas).isAfter(start) && moment(inCinemas).isAfter(start) &&
moment(inCinemas).isBefore(end) && moment(inCinemas).isBefore(end) &&
isBefore(movie.inCinemas) && isBefore(movie.inCinemas) &&
+5 -5
View File
@@ -2,8 +2,8 @@
flex: 1 0 14.28%; flex: 1 0 14.28%;
overflow: hidden; overflow: hidden;
min-height: 70px; min-height: 70px;
border-bottom: 1px solid var(--calendarBorderColor); border-bottom: 1px solid $calendarBorderColor;
border-left: 1px solid var(--calendarBorderColor); border-left: 1px solid $calendarBorderColor;
} }
.isSingleDay { .isSingleDay {
@@ -12,14 +12,14 @@
.dayOfMonth { .dayOfMonth {
padding-right: 5px; padding-right: 5px;
border-bottom: 1px solid var(--calendarBorderColor); border-bottom: 1px solid $calendarBorderColor;
text-align: right; text-align: right;
} }
.isToday { .isToday {
background-color: var(--calendarTodayBackgroundColor); background-color: $calendarTodayBackgroundColor;
} }
.isDifferentMonth { .isDifferentMonth {
color: var(--disabledColor); color: $disabledColor;
} }
+1 -1
View File
@@ -1,6 +1,6 @@
.days { .days {
display: flex; display: flex;
border-right: 1px solid var(--calendarBorderColor); border-right: 1px solid $calendarBorderColor;
} }
.day, .day,
+7 -7
View File
@@ -60,20 +60,20 @@ class CalendarDays extends Component {
this.setState({ todaysDate: todaysDate.toISOString() }); this.setState({ todaysDate: todaysDate.toISOString() });
this.updateTimeoutId = setTimeout(this.scheduleUpdate, diff); this.updateTimeoutId = setTimeout(this.scheduleUpdate, diff);
}; }
clearUpdateTimeout = () => { clearUpdateTimeout = () => {
if (this.updateTimeoutId) { if (this.updateTimeoutId) {
clearTimeout(this.updateTimeoutId); clearTimeout(this.updateTimeoutId);
} }
}; }
// //
// Listeners // Listeners
onEventModalOpenToggle = (isEventModalOpen) => { onEventModalOpenToggle = (isEventModalOpen) => {
this.setState({ isEventModalOpen }); this.setState({ isEventModalOpen });
}; }
onTouchStart = (event) => { onTouchStart = (event) => {
const touches = event.touches; const touches = event.touches;
@@ -92,7 +92,7 @@ class CalendarDays extends Component {
} }
this._touchStart = touchStart; this._touchStart = touchStart;
}; }
onTouchEnd = (event) => { onTouchEnd = (event) => {
const touches = event.changedTouches; const touches = event.changedTouches;
@@ -109,17 +109,17 @@ class CalendarDays extends Component {
} }
this._touchStart = null; this._touchStart = null;
}; }
onTouchCancel = (event) => { onTouchCancel = (event) => {
this._touchStart = null; this._touchStart = null;
}; }
onTouchMove = (event) => { onTouchMove = (event) => {
if (!this._touchStart) { if (!this._touchStart) {
return; return;
} }
}; }
// //
// Render // Render
+2 -2
View File
@@ -1,6 +1,6 @@
.dayOfWeek { .dayOfWeek {
flex: 1 0 14.28%; flex: 1 0 14.28%;
background-color: var(--calendarBackgroudColor); background-color: #e4eaec;
text-align: center; text-align: center;
} }
@@ -9,5 +9,5 @@
} }
.isToday { .isToday {
background-color: var(--calendarTodayBackgroundColor); background-color: $calendarTodayBackgroundColor;
} }
+2 -2
View File
@@ -47,13 +47,13 @@ class DaysOfWeek extends Component {
}); });
this.updateTimeoutId = setTimeout(this.scheduleUpdate, diff); this.updateTimeoutId = setTimeout(this.scheduleUpdate, diff);
}; }
clearUpdateTimeout = () => { clearUpdateTimeout = () => {
if (this.updateTimeoutId) { if (this.updateTimeoutId) {
clearTimeout(this.updateTimeoutId); clearTimeout(this.updateTimeoutId);
} }
}; }
// //
// Render // Render
+30 -21
View File
@@ -2,8 +2,8 @@
overflow-x: hidden; overflow-x: hidden;
margin: 4px 2px; margin: 4px 2px;
padding: 5px; padding: 5px;
border-bottom: 1px solid var(--calendarBorderColor); border-bottom: 1px solid $borderColor;
border-left: 4px solid var(--calendarBorderColor); border-left: 4px solid $borderColor;
font-size: 12px; font-size: 12px;
&:global(.colorImpaired) { &:global(.colorImpaired) {
@@ -15,10 +15,10 @@
composes: link from '~Components/Link/Link.css'; composes: link from '~Components/Link/Link.css';
display: block; display: block;
color: var(--defaultColor); color: $defaultColor;
&:hover { &:hover {
color: var(--defaultColor); color: $defaultColor;
text-decoration: none; text-decoration: none;
} }
} }
@@ -29,7 +29,7 @@
} }
.movieInfo { .movieInfo {
color: var(--calendarTextDim); color: $calendarTextDim;
} }
.movieTitle, .movieTitle,
@@ -40,7 +40,7 @@
} }
.movieTitle { .movieTitle {
color: var(--calendarTextDimAlternate); color: #3a3f51;
font-size: $defaultFontSize; font-size: $defaultFontSize;
} }
@@ -53,37 +53,46 @@
*/ */
.downloaded { .downloaded {
border-left-color: var(--successColor) !important; border-left-color: $successColor !important;
&:global(.colorImpaired) { &:global(.colorImpaired) {
border-left-color: color(var(--successColor), saturation(+15%)) !important; border-left-color: color($successColor, saturation(+15%)) !important;
} }
} }
.queue { .downloading {
border-left-color: var(--purple) !important; border-left-color: $purple !important;
} }
.unmonitored { .unmonitored {
border-left-color: var(--gray) !important; border-left-color: $gray !important;
}
.missingUnmonitored {
border-left-color: var(--warningColor) !important;
&:global(.colorImpaired) { &:global(.colorImpaired) {
background: repeating-linear-gradient(45deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px); background: repeating-linear-gradient(45deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
} }
} }
.missingMonitored { .onAir {
border-left-color: var(--dangerColor) !important; border-left-color: $warningColor !important;
&:global(.colorImpaired) { &:global(.colorImpaired) {
background: repeating-linear-gradient(90deg, var(--colorImpairedGradientDark), var(--colorImpairedGradientDark) 5px, var(--colorImpairedGradient) 5px, var(--colorImpairedGradient) 10px); background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
} }
} }
.continuing { .missing {
border-left-color: var(--primaryColor) !important; border-left-color: $dangerColor !important;
&:global(.colorImpaired) {
border-left-color: color($dangerColor saturation(+15%)) !important;
background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
}
}
.unreleased {
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,20 +38,20 @@ class CalendarEvent extends Component {
const isDownloading = !!(queueItem || grabbed); const isDownloading = !!(queueItem || grabbed);
const isMonitored = monitored; const isMonitored = monitored;
const statusStyle = getStatusStyle(null, isMonitored, hasFile, isAvailable, 'style', isDownloading); const statusStyle = getStatusStyle(hasFile, isDownloading, isAvailable, isMonitored);
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 = [];
if (inCinemas && moment(date).isSame(moment(inCinemas), 'day')) { if (moment(date).isSame(moment(inCinemas), 'day')) {
eventType.push('Cinemas'); eventType.push('Cinemas');
} }
if (physicalRelease && moment(date).isSame(moment(physicalRelease), 'day')) { if (moment(date).isSame(moment(physicalRelease), 'day')) {
eventType.push('Physical'); eventType.push('Physical');
} }
if (digitalRelease && moment(date).isSame(moment(digitalRelease), 'day')) { if (moment(date).isSame(moment(digitalRelease), 'day')) {
eventType.push('Digital'); eventType.push('Digital');
} }
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import QueueDetails from 'Activity/Queue/QueueDetails'; import QueueDetails from 'Activity/Queue/QueueDetails';
import CircularProgressBar from 'Components/CircularProgressBar'; import CircularProgressBar from 'Components/CircularProgressBar';
import colors from 'Styles/Variables/colors';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
function CalendarEventQueueDetails(props) { function CalendarEventQueueDetails(props) {
@@ -34,7 +35,7 @@ function CalendarEventQueueDetails(props) {
progress={progress} progress={progress}
size={20} size={20}
strokeWidth={2} strokeWidth={2}
strokeColor={'#7a43b6'} strokeColor={colors.purple}
/> />
</div> </div>
} }
@@ -71,7 +71,7 @@ class CalendarHeader extends Component {
this.setState({ view }, () => { this.setState({ view }, () => {
this.props.onViewChange(view); this.props.onViewChange(view);
}); });
}; }
// //
// Render // Render
@@ -41,19 +41,19 @@ class CalendarHeaderConnector extends Component {
onViewChange = (view) => { onViewChange = (view) => {
this.props.setCalendarView({ view }); this.props.setCalendarView({ view });
}; }
onTodayPress = () => { onTodayPress = () => {
this.props.gotoCalendarToday(); this.props.gotoCalendarToday();
}; }
onPreviousPress = () => { onPreviousPress = () => {
this.props.gotoCalendarPreviousRange(); this.props.gotoCalendarPreviousRange();
}; }
onNextPress = () => { onNextPress = () => {
this.props.gotoCalendarNextRange(); this.props.gotoCalendarNextRange();
}; }
// //
// Render // Render

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