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 6df45eb6af Fixed: Don't get all movies if not scanning after refresh 2020-07-31 20:56:05 -04:00
1037 changed files with 9856 additions and 27597 deletions
-213
View File
@@ -2,12 +2,6 @@
# editorconfig.org # editorconfig.org
root = true root = true
# NOTE: Requires **VS2019 16.3** or later
# Stylecop.ruleset
# Description: Rules for Radarr
# Code files
[*.cs] [*.cs]
charset = utf-8 charset = utf-8
trim_trailing_whitespace = true trim_trailing_whitespace = true
@@ -44,213 +38,6 @@ csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = true:suggestion csharp_style_var_elsewhere = true:suggestion
# Stylecop Rules
dotnet_diagnostic.SA0001.severity = none
dotnet_diagnostic.SA1005.severity = none
dotnet_diagnostic.SA1025.severity = none
dotnet_diagnostic.SA1101.severity = none
dotnet_diagnostic.SA1116.severity = none
dotnet_diagnostic.SA1118.severity = none
dotnet_diagnostic.SA1122.severity = none
dotnet_diagnostic.SA1201.severity = suggestion
dotnet_diagnostic.SA1202.severity = suggestion
dotnet_diagnostic.SA1204.severity = suggestion
dotnet_diagnostic.SA1300.severity = none
dotnet_diagnostic.SA1303.severity = none
dotnet_diagnostic.SA1304.severity = none
dotnet_diagnostic.SA1306.severity = none
dotnet_diagnostic.SA1309.severity = none
dotnet_diagnostic.SA1310.severity = none
dotnet_diagnostic.SA1401.severity = none
dotnet_diagnostic.SA1402.severity = none
dotnet_diagnostic.SA1404.severity = suggestion
dotnet_diagnostic.SA1405.severity = suggestion
dotnet_diagnostic.SA1406.severity = suggestion
dotnet_diagnostic.SA1410.severity = suggestion
dotnet_diagnostic.SA1411.severity = suggestion
dotnet_diagnostic.SA1413.severity = none
dotnet_diagnostic.SA1516.severity = none
dotnet_diagnostic.SA1600.severity = none
dotnet_diagnostic.SA1601.severity = none
dotnet_diagnostic.SA1602.severity = none
dotnet_diagnostic.SA1604.severity = none
dotnet_diagnostic.SA1605.severity = none
dotnet_diagnostic.SA1606.severity = none
dotnet_diagnostic.SA1607.severity = none
dotnet_diagnostic.SA1608.severity = none
dotnet_diagnostic.SA1610.severity = none
dotnet_diagnostic.SA1611.severity = none
dotnet_diagnostic.SA1612.severity = none
dotnet_diagnostic.SA1613.severity = none
dotnet_diagnostic.SA1614.severity = none
dotnet_diagnostic.SA1615.severity = none
dotnet_diagnostic.SA1616.severity = none
dotnet_diagnostic.SA1617.severity = none
dotnet_diagnostic.SA1618.severity = none
dotnet_diagnostic.SA1619.severity = none
dotnet_diagnostic.SA1620.severity = none
dotnet_diagnostic.SA1621.severity = none
dotnet_diagnostic.SA1622.severity = none
dotnet_diagnostic.SA1623.severity = none
dotnet_diagnostic.SA1624.severity = none
dotnet_diagnostic.SA1625.severity = none
dotnet_diagnostic.SA1626.severity = none
dotnet_diagnostic.SA1627.severity = none
dotnet_diagnostic.SA1629.severity = none
dotnet_diagnostic.SA1633.severity = none
dotnet_diagnostic.SA1634.severity = none
dotnet_diagnostic.SA1635.severity = none
dotnet_diagnostic.SA1636.severity = none
dotnet_diagnostic.SA1637.severity = none
dotnet_diagnostic.SA1638.severity = none
dotnet_diagnostic.SA1640.severity = none
dotnet_diagnostic.SA1641.severity = none
dotnet_diagnostic.SA1642.severity = none
dotnet_diagnostic.SA1643.severity = none
dotnet_diagnostic.SA1648.severity = none
dotnet_diagnostic.SA1649.severity = none
dotnet_diagnostic.SA1651.severity = none
dotnet_diagnostic.SX1101.severity = warning
dotnet_diagnostic.SX1309.severity = warning
# Microsoft Analyzers that fail and need to be sorted thru
dotnet_diagnostic.ASP0000.severity = suggestion
dotnet_diagnostic.CA1000.severity = suggestion
dotnet_diagnostic.CA1001.severity = suggestion
dotnet_diagnostic.CA1003.severity = suggestion
dotnet_diagnostic.CA1008.severity = suggestion
dotnet_diagnostic.CA1010.severity = suggestion
dotnet_diagnostic.CA1012.severity = suggestion
dotnet_diagnostic.CA1014.severity = suggestion
dotnet_diagnostic.CA1016.severity = suggestion
dotnet_diagnostic.CA1017.severity = suggestion
dotnet_diagnostic.CA1018.severity = suggestion
dotnet_diagnostic.CA1019.severity = suggestion
dotnet_diagnostic.CA1021.severity = suggestion
dotnet_diagnostic.CA1024.severity = suggestion
dotnet_diagnostic.CA1027.severity = suggestion
dotnet_diagnostic.CA1028.severity = suggestion
dotnet_diagnostic.CA1030.severity = suggestion
dotnet_diagnostic.CA1031.severity = suggestion
dotnet_diagnostic.CA1032.severity = suggestion
dotnet_diagnostic.CA1033.severity = suggestion
dotnet_diagnostic.CA1034.severity = suggestion
dotnet_diagnostic.CA1036.severity = suggestion
dotnet_diagnostic.CA1040.severity = suggestion
dotnet_diagnostic.CA1041.severity = suggestion
dotnet_diagnostic.CA1043.severity = suggestion
dotnet_diagnostic.CA1044.severity = suggestion
dotnet_diagnostic.CA1050.severity = suggestion
dotnet_diagnostic.CA1051.severity = suggestion
dotnet_diagnostic.CA1052.severity = suggestion
dotnet_diagnostic.CA1054.severity = suggestion
dotnet_diagnostic.CA1055.severity = suggestion
dotnet_diagnostic.CA1056.severity = suggestion
dotnet_diagnostic.CA1058.severity = suggestion
dotnet_diagnostic.CA1060.severity = suggestion
dotnet_diagnostic.CA1061.severity = suggestion
dotnet_diagnostic.CA1062.severity = suggestion
dotnet_diagnostic.CA1063.severity = suggestion
dotnet_diagnostic.CA1064.severity = suggestion
dotnet_diagnostic.CA1065.severity = suggestion
dotnet_diagnostic.CA1066.severity = suggestion
dotnet_diagnostic.CA1067.severity = suggestion
dotnet_diagnostic.CA1068.severity = suggestion
dotnet_diagnostic.CA1069.severity = suggestion
dotnet_diagnostic.CA1200.severity = suggestion
dotnet_diagnostic.CA1303.severity = suggestion
dotnet_diagnostic.CA1304.severity = suggestion
dotnet_diagnostic.CA1305.severity = suggestion
dotnet_diagnostic.CA1307.severity = suggestion
dotnet_diagnostic.CA1308.severity = suggestion
dotnet_diagnostic.CA1401.severity = suggestion
dotnet_diagnostic.CA1507.severity = suggestion
dotnet_diagnostic.CA1707.severity = suggestion
dotnet_diagnostic.CA1710.severity = suggestion
dotnet_diagnostic.CA1712.severity = suggestion
dotnet_diagnostic.CA1714.severity = suggestion
dotnet_diagnostic.CA1715.severity = suggestion
dotnet_diagnostic.CA1716.severity = suggestion
dotnet_diagnostic.CA1717.severity = suggestion
dotnet_diagnostic.CA1720.severity = suggestion
dotnet_diagnostic.CA1721.severity = suggestion
dotnet_diagnostic.CA1724.severity = suggestion
dotnet_diagnostic.CA1801.severity = suggestion
dotnet_diagnostic.CA1802.severity = suggestion
dotnet_diagnostic.CA1805.severity = suggestion
dotnet_diagnostic.CA1806.severity = suggestion
dotnet_diagnostic.CA1810.severity = suggestion
dotnet_diagnostic.CA1812.severity = suggestion
dotnet_diagnostic.CA1814.severity = suggestion
dotnet_diagnostic.CA1815.severity = suggestion
dotnet_diagnostic.CA1816.severity = suggestion
dotnet_diagnostic.CA1819.severity = suggestion
dotnet_diagnostic.CA1822.severity = suggestion
dotnet_diagnostic.CA1823.severity = suggestion
dotnet_diagnostic.CA1824.severity = suggestion
dotnet_diagnostic.CA2000.severity = suggestion
dotnet_diagnostic.CA2002.severity = suggestion
dotnet_diagnostic.CA2007.severity = suggestion
dotnet_diagnostic.CA2008.severity = suggestion
dotnet_diagnostic.CA2009.severity = suggestion
dotnet_diagnostic.CA2010.severity = suggestion
dotnet_diagnostic.CA2011.severity = suggestion
dotnet_diagnostic.CA2012.severity = suggestion
dotnet_diagnostic.CA2013.severity = suggestion
dotnet_diagnostic.CA2100.severity = suggestion
dotnet_diagnostic.CA2101.severity = suggestion
dotnet_diagnostic.CA2119.severity = suggestion
dotnet_diagnostic.CA2153.severity = suggestion
dotnet_diagnostic.CA2200.severity = suggestion
dotnet_diagnostic.CA2207.severity = suggestion
dotnet_diagnostic.CA2208.severity = suggestion
dotnet_diagnostic.CA2211.severity = suggestion
dotnet_diagnostic.CA2213.severity = suggestion
dotnet_diagnostic.CA2214.severity = suggestion
dotnet_diagnostic.CA2215.severity = suggestion
dotnet_diagnostic.CA2216.severity = suggestion
dotnet_diagnostic.CA2219.severity = suggestion
dotnet_diagnostic.CA2225.severity = suggestion
dotnet_diagnostic.CA2226.severity = suggestion
dotnet_diagnostic.CA2227.severity = suggestion
dotnet_diagnostic.CA2229.severity = suggestion
dotnet_diagnostic.CA2231.severity = suggestion
dotnet_diagnostic.CA2234.severity = suggestion
dotnet_diagnostic.CA2235.severity = suggestion
dotnet_diagnostic.CA2237.severity = suggestion
dotnet_diagnostic.CA2241.severity = suggestion
dotnet_diagnostic.CA2242.severity = suggestion
dotnet_diagnostic.CA2243.severity = suggestion
dotnet_diagnostic.CA2244.severity = suggestion
dotnet_diagnostic.CA2245.severity = suggestion
dotnet_diagnostic.CA2246.severity = suggestion
dotnet_diagnostic.CA3061.severity = suggestion
dotnet_diagnostic.CA3075.severity = suggestion
dotnet_diagnostic.CA3076.severity = suggestion
dotnet_diagnostic.CA3077.severity = suggestion
dotnet_diagnostic.CA3147.severity = suggestion
dotnet_diagnostic.CA5350.severity = suggestion
dotnet_diagnostic.CA5351.severity = suggestion
dotnet_diagnostic.CA5359.severity = suggestion
dotnet_diagnostic.CA5360.severity = suggestion
dotnet_diagnostic.CA5363.severity = suggestion
dotnet_diagnostic.CA5364.severity = suggestion
dotnet_diagnostic.CA5365.severity = suggestion
dotnet_diagnostic.CA5366.severity = suggestion
dotnet_diagnostic.CA5368.severity = suggestion
dotnet_diagnostic.CA5369.severity = suggestion
dotnet_diagnostic.CA5370.severity = suggestion
dotnet_diagnostic.CA5371.severity = suggestion
dotnet_diagnostic.CA5372.severity = suggestion
dotnet_diagnostic.CA5373.severity = suggestion
dotnet_diagnostic.CA5374.severity = suggestion
dotnet_diagnostic.CA5379.severity = suggestion
dotnet_diagnostic.CA5384.severity = suggestion
dotnet_diagnostic.CA5385.severity = suggestion
dotnet_diagnostic.CA5397.severity = suggestion
[*.{js,html,js,hbs,less,css}] [*.{js,html,js,hbs,less,css}]
charset = utf-8 charset = utf-8
trim_trailing_whitespace = true trim_trailing_whitespace = true
+1 -1
View File
@@ -6,7 +6,7 @@
**Just because you receive an exception in your logs, doesn't mean it's a bug and should be reported here. Often it's something else, such as a permission error. If you are unsure ask on the Discord or Subreddit first.** **Just because you receive an exception in your logs, doesn't mean it's a bug and should be reported here. Often it's something else, such as a permission error. If you are unsure ask on the Discord or Subreddit first.**
Visit our [Discord server](https://discord.gg/r5wJPt9) or [Subreddit](https://reddit.com/r/radarr) for support or longer discussions. Support questions posed on here will be closed immediately. Visit our [Discord server](https://discord.gg/NWYch8M) or [Subreddit](https://reddit.com/r/radarr) for support or longer discussions. Support questions posed on here will be closed immediately.
Provide a description of the feature request or bug here, the more details the better. Provide a description of the feature request or bug here, the more details the better.
Please also include the following if you are reporting a bug. If you do not include it, the issue will probably be closed as we cannot help you. --> Please also include the following if you are reporting a bug. If you do not include it, the issue will probably be closed as we cannot help you. -->
+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://discord.gg/r5wJPt9 url: https://discord.gg/AD3UP37
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
-1
View File
@@ -6,7 +6,6 @@ YES | NO
#### Todos #### Todos
- [ ] Tests - [ ] Tests
- [ ] Translation Keys
#### Issues Fixed or Closed by this PR #### Issues Fixed or Closed by this PR
+1 -4
View File
@@ -7,10 +7,7 @@ exemptLabels:
- feature request - feature request
- parser - parser
- confirmed - confirmed
- sonarr-pull - aphrodite
- lidarr-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
+1 -1
View File
@@ -6,7 +6,7 @@ supportLabel: support
# to a support page, or set to `false` to disable # to a support page, or set to `false` to disable
supportComment: > supportComment: >
We use the issue tracker exclusively for bug reports and feature requests. 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) However, this issue appears to be a support request. Please hop over onto our [Discord](https://discord.gg/ZDmT7qb) or [Subreddit](https://reddit.com/r/radarr)
# Whether to close issues marked as support requests # Whether to close issues marked as support requests
close: true close: true
# Whether to lock issues marked as support requests # Whether to lock issues marked as support requests
+1 -1
View File
@@ -14,7 +14,7 @@ See the readme for information on setting up your development environment.
- Rebase from Radarr's develop branch, don't merge - Rebase from Radarr's develop branch, don't merge
- Make meaningful commits, or squash them - 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 - 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 - Reach out to us on the forums or on IRC if you have any questions
- Add tests (unit/integration) - Add tests (unit/integration)
- Commit with *nix line endings for consistency (We checkout Windows and commit *nix) - 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 - One feature/bug fix per pull request to keep things clean and easy to understand
+15 -17
View File
@@ -13,34 +13,28 @@ The project was inspired by other Usenet/BitTorrent movie downloaders such as Co
## Getting Started ## Getting Started
[![Installation](https://img.shields.io/badge/wiki-installation-brightgreen.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/wiki/Installation) [![Installation](https://img.shields.io/badge/wiki-installation-brightgreen.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/wiki/Installation)
[![Docker](https://img.shields.io/badge/wiki-docker-1488C6.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/wiki/Docker) [![Docker](https://img.shields.io/badge/wiki-docker-1488C6.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/wiki/Docker)
[![Setup Guide](https://img.shields.io/badge/wiki-setup_guide-orange.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/wiki/Setup-Guide) [![Setup Guide](https://img.shields.io/badge/wiki-setup_guide-orange.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/wiki/Setup-Guide)
[![FAQ](https://img.shields.io/badge/wiki-FAQ-BF55EC.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/wiki/FAQ) [![FAQ](https://img.shields.io/badge/wiki-FAQ-BF55EC.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/wiki/FAQ)
If you are using Docker please ensure your Docker paths are setup correctly using [this guide to facilitate](https://old.reddit.com/r/usenet/wiki/docker) hardlinks and minimize permissions issues.
* [Install Radarr for your desired OS](https://github.com/Radarr/Radarr/wiki/Installation) *or* use [Docker](https://github.com/Radarr/Radarr/wiki/Docker) * [Install Radarr for your desired OS](https://github.com/Radarr/Radarr/wiki/Installation) *or* use [Docker](https://github.com/Radarr/Radarr/wiki/Docker)
* *For Linux users*, run `radarr` and *optionally* have [Radarr start automatically](https://github.com/Radarr/Radarr/wiki/Autostart-on-Linux) * *For Linux users*, run `radarr` and *optionally* have [Radarr start automatically](https://github.com/Radarr/Radarr/wiki/Autostart-on-Linux)
* Connect to the UI through <http://localhost:7878> or <http://your-ip:7878> in your web browser * Connect to the UI through <http://localhost:7878> or <http://your-ip:7878> in your web browser
* See the [Setup Guide](https://github.com/Radarr/Radarr/wiki/Setup-Guide) for further configuration * See the [Setup Guide](https://github.com/Radarr/Radarr/wiki/Setup-Guide) for further configuration
## Downloads ## Downloads
Please note that v0.2 will only have critical bugs resolved as of August 2020. Any additional development or features will be soley in V3.
Each push to the "develop" branch creates a build on "nightly" release channel (release channel is the "branch" within radarr's settings), once we push a build to Github it will show up on "develop" release channel. | Release Type | Branch: develop (stable) | Branch: nightly (semi-unstable) | Branch: aphrodite (very-unstable) |
|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Binary Releases | [![GitHub Releases](https://img.shields.io/badge/downloads-releases-brightgreen.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/releases) | [![AppVeyor Builds](https://img.shields.io/badge/downloads-nightly-green.svg?maxAge=60&style=flat-square)](https://ci.appveyor.com/project/galli-leo/radarr-usby1/branch/develop/artifacts) | |
| Release Channel Type | Branch: develop (stable) (v0.2) | Branch: nightly (semi-unstable) (v3.0) | | Docker | [![Docker release](https://img.shields.io/badge/linuxserver-radarr:latest-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://hub.docker.com/r/linuxserver/radarr) | [![Docker nightly](https://img.shields.io/badge/linuxserver-radarr:nightly-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://hub.docker.com/r/linuxserver/radarr) | [![Docker aphrodite](https://img.shields.io/badge/linuxserver-radarr:preview-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://hub.docker.com/r/linuxserver/radarr) |
|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Docker | [![Docker release](https://img.shields.io/badge/hotio-radarr:latest-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://hub.docker.com/r/hotio/radarr) | [![Docker nightly](https://img.shields.io/badge/hotio-radarr:unstable-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://hub.docker.com/r/hotio/radarr) | [![Docker aphrodite](https://img.shields.io/badge/hotio-radarr:aphrodite-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://hub.docker.com/r/hotio/radarr) |
| Binary Releases | [![GitHub Releases](https://img.shields.io/badge/downloads-releases-brightgreen.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/releases) | [![Azure Build](https://img.shields.io/badge/downloads-Windows_X64-green.svg?maxAge=60&style=flat-square)](https://radarr.servarr.com/v1/update/nightly/updatefile?os=windows&runtime=netcore&arch=x64) <br> [![Azure Build](https://img.shields.io/badge/downloads-Linux_X64-green.svg?maxAge=60&style=flat-square)](https://radarr.servarr.com/v1/update/nightly/updatefile?os=linux&runtime=netcore&arch=x64) <br> [![Azure Build](https://img.shields.io/badge/downloads-Linux_ARM64-green.svg?maxAge=60&style=flat-square)](https://radarr.servarr.com/v1/update/nightly/updatefile?os=linux&runtime=netcore&arch=arm64) [![Azure Build](https://img.shields.io/badge/downloads-Linux_ARM-green.svg?maxAge=60&style=flat-square)](https://radarr.servarr.com/v1/update/nightly/updatefile?os=linux&runtime=netcore&arch=arm) <br> [![Azure Build](https://img.shields.io/badge/downloads-macOS-green.svg?maxAge=60&style=flat-square)](https://radarr.servarr.com/v1/update/nightly/updatefile?os=osx&runtime=netcore&arch=x64)
| Docker - lsio | [![Docker release](https://img.shields.io/badge/linuxserver-radarr:latest-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://hub.docker.com/r/linuxserver/radarr) | [![Docker nightly](https://img.shields.io/badge/linuxserver-radarr:nightly-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://hub.docker.com/r/linuxserver/radarr) |
| Docker - hotio | [![Docker release](https://img.shields.io/badge/hotio-radarr:latest-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://hub.docker.com/r/hotio/radarr) | [![Docker nightly](https://img.shields.io/badge/hotio-radarr:nightly-blue.svg?colorB=1488C6&maxAge=60&style=flat-square)](https://hub.docker.com/r/hotio/radarr) |
## Support ## Support
[![Discord](https://img.shields.io/badge/discord-chat-r5wJPt9.svg?maxAge=60&style=flat-square)](https://discord.gg/r5wJPt9) [![Discord](https://img.shields.io/badge/discord-chat-7289DA.svg?maxAge=60&style=flat-square)](https://discord.gg/AD3UP37)
[![Reddit](https://img.shields.io/badge/reddit-discussion-FF4500.svg?maxAge=60&style=flat-square)](https://www.reddit.com/r/radarr) [![Reddit](https://img.shields.io/badge/reddit-discussion-FF4500.svg?maxAge=60&style=flat-square)](https://www.reddit.com/r/radarr)
[![Feathub](https://img.shields.io/badge/feathub-requests-lightgrey.svg?maxAge=60&style=flat-square)](http://feathub.com/Radarr/Radarr)
[![GitHub](https://img.shields.io/badge/github-issues-red.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/issues) [![GitHub](https://img.shields.io/badge/github-issues-red.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/issues)
[![GitHub Wiki](https://img.shields.io/badge/github-wiki-181717.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/wiki) [![GitHub Wiki](https://img.shields.io/badge/github-wiki-181717.svg?maxAge=60&style=flat-square)](https://github.com/Radarr/Radarr/wiki)
@@ -78,14 +72,18 @@ Radarr is currently undergoing rapid development and pull requests are actively
* New TorrentPotato Indexer * New TorrentPotato Indexer
* Torznab Indexer now supports Movies (Works well with [Jackett](https://github.com/Jackett/Jackett)) * Torznab Indexer now supports Movies (Works well with [Jackett](https://github.com/Jackett/Jackett))
* Scanning PreDB to know when a new release is available * Scanning PreDB to know when a new release is available
* Importing movies from various online sources, such as IMDb Watchlists or Trakt (v3) (A complete list can be found [here](https://github.com/Radarr/Radarr/issues/114)) * Importing movies from various online sources, such as IMDb Watchlists (A complete list can be found [here](https://github.com/Radarr/Radarr/issues/114))
* Full integration with Kodi, Plex (notification, library update) * Full integration with Kodi, Plex (notification, library update)
* And a new beautiful UI (v3) * And 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
#### [Feature Requests](https://github.com/Radarr/Radarr/issues/new?assignees=&labels=feature+request&template=feature_request.md&title=) ### Planned Features
See the [Roadmap blogpost](https://blog.radarr.video/development/update/2018/11/11/roadmap-update.html) for an overview of planned features.
#### [Feature Requests](http://feathub.com/Radarr/Radarr)
## Configuring the Development Environment ## Configuring the Development Environment
+41 -60
View File
@@ -13,17 +13,17 @@ variables:
buildName: '$(Build.SourceBranchName).$(radarrVersion)' buildName: '$(Build.SourceBranchName).$(radarrVersion)'
sentryOrg: 'servarr' sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com' sentryUrl: 'https://sentry.servarr.com'
dotnetVersion: '3.1.401' dotnetVersion: '3.1.302'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
trigger: trigger:
branches: branches:
include: include:
- develop - develop
- master - aphrodite
pr: pr:
- develop - develop
- aphrodite
stages: stages:
- stage: Setup - stage: Setup
@@ -39,7 +39,7 @@ stages:
displayName: Set Build Name displayName: Set Build Name
- bash: | - bash: |
if [[ $BUILD_REASON == "PullRequest" ]]; then if [[ $BUILD_REASON == "PullRequest" ]]; then
git diff origin/develop...HEAD --name-only | grep -E "^(src/|azure-pipelines.yml)" git diff origin/aphrodite...HEAD --name-only | grep -E "^(src/|azure-pipelines.yml)"
echo $? > not_backend_update echo $? > not_backend_update
else else
echo 0 > not_backend_update echo 0 > not_backend_update
@@ -68,9 +68,6 @@ stages:
pool: pool:
vmImage: $(imageName) vmImage: $(imageName)
variables:
# Disable stylecop here - linting errors get caught by the analyze task
EnableAnalyzers: 'false'
steps: steps:
- checkout: self - checkout: self
submodules: true submodules: true
@@ -139,19 +136,10 @@ stages:
- checkout: self - checkout: self
submodules: true submodules: true
fetchDepth: 1 fetchDepth: 1
- task: Cache@2
inputs:
key: 'yarn | "$(osName)" | yarn.lock'
restoreKeys: |
yarn | "$(osName)"
yarn
path: $(yarnCacheFolder)
displayName: Cache Yarn packages
- bash: ./build.sh --frontend - bash: ./build.sh --frontend
displayName: Build Radarr Frontend displayName: Build Radarr Frontend
env: env:
FORCE_COLOR: 0 FORCE_COLOR: 0
YARN_CACHE_FOLDER: $(yarnCacheFolder)
- publish: $(outputFolder) - publish: $(outputFolder)
artifact: '$(osName)Frontend' artifact: '$(osName)Frontend'
displayName: Publish Frontend displayName: Publish Frontend
@@ -301,22 +289,14 @@ stages:
sentry-cli releases new --finalize -p radarr -p radarr-ui -p radarr-update "${RELEASENAME}" sentry-cli releases new --finalize -p radarr -p radarr-ui -p radarr-update "${RELEASENAME}"
sentry-cli releases -p radarr-ui files "${RELEASENAME}" upload-sourcemaps _output/UI/ --rewrite sentry-cli releases -p radarr-ui files "${RELEASENAME}" upload-sourcemaps _output/UI/ --rewrite
sentry-cli releases set-commits --auto "${RELEASENAME}" sentry-cli releases set-commits --auto "${RELEASENAME}"
if [[ ${BUILD_SOURCEBRANCH} == "refs/heads/develop" ]]; then sentry-cli releases deploys "${RELEASENAME}" new -e aphrodite
sentry-cli releases deploys "${RELEASENAME}" new -e nightly
else
sentry-cli releases deploys "${RELEASENAME}" new -e production
fi
if [ $? -gt 0 ]; then if [ $? -gt 0 ]; then
echo "##vso[task.logissue type=warning]Error uploading source maps." echo "##vso[task.logissue type=warning]Error uploading source maps."
fi fi
exit 0 exit 0
displayName: Publish Sentry Source Maps displayName: Publish Sentry Source Maps
condition: | continueOnError: true
or condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/aphrodite'))
(
and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop')),
and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
)
env: env:
SENTRY_AUTH_TOKEN: $(sentryAuthTokenServarr) SENTRY_AUTH_TOKEN: $(sentryAuthTokenServarr)
SENTRY_ORG: $(sentryOrg) SENTRY_ORG: $(sentryOrg)
@@ -386,6 +366,11 @@ stages:
- powershell: Set-Service SCardSvr -StartupType Manual - powershell: Set-Service SCardSvr -StartupType Manual
displayName: Enable Windows Test Service displayName: Enable Windows Test Service
condition: and(succeeded(), eq(variables['osName'], 'Windows')) condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- bash: |
wget https://github.com/acoustid/chromaprint/releases/download/v1.4.3/chromaprint-fpcalc-1.4.3-linux-x86_64.tar.gz
sudo tar xf chromaprint-fpcalc-1.4.3-linux-x86_64.tar.gz --strip-components=1 --directory /usr/bin
displayName: Install fpcalc
condition: and(succeeded(), eq(variables['osName'], 'Linux'))
- bash: | - bash: |
SYMLINK=6_6_0 SYMLINK=6_6_0
MONOPREFIX=/Library/Frameworks/Mono.framework/Versions/$SYMLINK MONOPREFIX=/Library/Frameworks/Mono.framework/Versions/$SYMLINK
@@ -417,6 +402,10 @@ 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:
mono508:
testName: 'Mono 5.8'
artifactName: LinuxTests
containerImage: servarr/testimages:mono-5.8
mono520: mono520:
testName: 'Mono 5.20' testName: 'Mono 5.20'
artifactName: LinuxTests artifactName: LinuxTests
@@ -566,6 +555,11 @@ 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:
mono508:
testName: 'Mono 5.8'
artifactName: LinuxTests
containerImage: servarr/testimages:mono-5.8
pattern: 'Radarr.**.linux.tar.gz'
mono520: mono520:
testName: 'Mono 5.20' testName: 'Mono 5.20'
artifactName: LinuxTests artifactName: LinuxTests
@@ -583,7 +577,7 @@ stages:
pattern: 'Radarr.**.linux.tar.gz' pattern: 'Radarr.**.linux.tar.gz'
alpine: alpine:
testName: 'Musl Net Core' testName: 'Musl Net Core'
artifactName: LinuxMuslCoreTests artifactName: LinuxCoreTests
containerImage: servarr/testimages:alpine containerImage: servarr/testimages:alpine
pattern: 'Radarr.**.linux-musl-core-x64.tar.gz' pattern: 'Radarr.**.linux-musl-core-x64.tar.gz'
pool: pool:
@@ -648,14 +642,14 @@ stages:
failBuild: true failBuild: true
Mac: Mac:
osName: 'Mac' osName: 'Mac'
imageName: 'macos-10.14' imageName: 'macos-10.14' # Fails due to firefox not being installed on image
pattern: 'Radarr.**.osx-core-x64.tar.gz' pattern: 'Radarr.**.osx-core-x64.tar.gz'
failBuild: true failBuild: false
Windows: Windows:
osName: 'Windows' osName: 'Windows'
imageName: 'windows-2019' imageName: 'windows-2019'
pattern: 'Radarr.**.windows-core-x64.zip' pattern: 'Radarr.**.windows-core-x64.zip'
failBuild: true failBuild: $(failOnAutomationFailure)
pool: pool:
vmImage: $(imageName) vmImage: $(imageName)
@@ -688,21 +682,24 @@ stages:
mkdir -p ./bin/ mkdir -p ./bin/
cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Radarr/. ./bin/ cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Radarr/. ./bin/
displayName: Move Package Contents displayName: Move Package Contents
- bash: |
if [[ $OSNAME == "Mac" ]]; then
url=https://github.com/mozilla/geckodriver/releases/download/v0.26.0/geckodriver-v0.26.0-macos.tar.gz
elif [[ $OSNAME == "Linux" ]]; then
url=https://github.com/mozilla/geckodriver/releases/download/v0.26.0/geckodriver-v0.26.0-linux64.tar.gz
else
echo "Unhandled OS"
exit 1
fi
curl -s -L "$url" | tar -xz
chmod +x geckodriver
mv geckodriver _tests
displayName: Install Gecko Driver
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
- bash: | - bash: |
chmod a+x ${TESTSFOLDER}/test.sh chmod a+x ${TESTSFOLDER}/test.sh
${TESTSFOLDER}/test.sh ${OSNAME} Automation Test ${TESTSFOLDER}/test.sh ${OSNAME} Automation Test
displayName: Run Automation Tests displayName: Run Integration Tests
- task: CopyFiles@2
displayName: 'Copy Screenshot to: $(Build.ArtifactStagingDirectory)'
inputs:
SourceFolder: '$(Build.SourcesDirectory)'
Contents: |
**/*_test_screenshot.png
TargetFolder: '$(Build.ArtifactStagingDirectory)/screenshots'
- publish: $(Build.ArtifactStagingDirectory)/screenshots
artifact: '$(osName)AutomationScreenshots'
displayName: Publish Screenshot Bundle
condition: and(succeeded(), eq(variables['System.JobAttempt'], '1'))
- task: PublishTestResults@2 - task: PublishTestResults@2
inputs: inputs:
testResultsFormat: 'NUnit' testResultsFormat: 'NUnit'
@@ -750,19 +747,10 @@ stages:
- checkout: self - checkout: self
submodules: true submodules: true
fetchDepth: 1 fetchDepth: 1
- task: Cache@2
inputs:
key: 'yarn | "$(osName)" | yarn.lock'
restoreKeys: |
yarn | "$(osName)"
yarn
path: $(yarnCacheFolder)
displayName: Cache Yarn packages
- bash: ./build.sh --lint - bash: ./build.sh --lint
displayName: Lint Radarr Frontend displayName: Lint Radarr Frontend
env: env:
FORCE_COLOR: 0 FORCE_COLOR: 0
YARN_CACHE_FOLDER: $(yarnCacheFolder)
- job: Analyze_Frontend - job: Analyze_Frontend
displayName: Frontend displayName: Frontend
@@ -851,15 +839,8 @@ stages:
- job: - job:
displayName: Discord Notification displayName: Discord Notification
pool: pool:
vmImage: 'windows-2019' vmImage: 'ubuntu-18.04'
steps: steps:
- task: DownloadPipelineArtifact@2
continueOnError: true
displayName: Download Screenshot Artifact
inputs:
buildType: 'current'
artifactName: 'WindowsAutomationScreenshots'
targetPath: $(Build.SourcesDirectory)
- checkout: none - checkout: none
- powershell: | - 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'))
+1 -1
View File
@@ -235,7 +235,7 @@ PackageTests()
# geckodriver.exe isn't copied by dotnet publish # geckodriver.exe isn't copied by dotnet publish
if [ "$runtime" = "win-x64" ]; if [ "$runtime" = "win-x64" ];
then then
curl -Lso gecko.zip "https://github.com/mozilla/geckodriver/releases/download/v0.27.0/geckodriver-v0.27.0-win64.zip" curl -Lso gecko.zip "https://github.com/mozilla/geckodriver/releases/download/v0.26.0/geckodriver-v0.26.0-win64.zip"
unzip -o gecko.zip unzip -o gecko.zip
cp geckodriver.exe "$testPackageFolder/$framework/win-x64/publish" cp geckodriver.exe "$testPackageFolder/$framework/win-x64/publish"
fi fi
+1 -1
View File
@@ -75,7 +75,7 @@
"function-parentheses-newline-inside": "never-multi-line", "function-parentheses-newline-inside": "never-multi-line",
"function-parentheses-space-inside": "never", "function-parentheses-space-inside": "never",
"function-url-quotes": "always", "function-url-quotes": "always",
"function-url-scheme-disallowed-list": [ "function-url-scheme-blacklist": [
"data" "data"
], ],
"function-whitespace-after": "always", "function-whitespace-after": "always",
+9 -14
View File
@@ -7,7 +7,6 @@ const errorHandler = require('./helpers/errorHandler');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackPluginHtmlTags = require('html-webpack-plugin/lib/html-tags');
const TerserPlugin = require('terser-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin');
const uiFolder = 'UI'; const uiFolder = 'UI';
@@ -15,7 +14,7 @@ const frontendFolder = path.join(__dirname, '..');
const srcFolder = path.join(frontendFolder, 'src'); const srcFolder = path.join(frontendFolder, 'src');
const isProduction = process.argv.indexOf('--production') > -1; const isProduction = process.argv.indexOf('--production') > -1;
const isProfiling = isProduction && process.argv.indexOf('--profile') > -1; const isProfiling = isProduction && process.argv.indexOf('--profile') > -1;
const inlineWebWorkers = 'no-fallback'; const inlineWebWorkers = true;
const distFolder = path.resolve(frontendFolder, '..', '_output', uiFolder); const distFolder = path.resolve(frontendFolder, '..', '_output', uiFolder);
@@ -33,19 +32,14 @@ const cssVarsFiles = [
].map(require.resolve); ].map(require.resolve);
// Override the way HtmlWebpackPlugin injects the scripts // Override the way HtmlWebpackPlugin injects the scripts
// TODO: Find a better way to get these paths without
HtmlWebpackPlugin.prototype.injectAssetsIntoHtml = function(html, assets, assetTags) { HtmlWebpackPlugin.prototype.injectAssetsIntoHtml = function(html, assets, assetTags) {
const head = assetTags.headTags.map((v) => { const head = assetTags.head.map((v) => {
const href = v.attributes.href v.attributes = { rel: 'stylesheet', type: 'text/css', href: `/${v.attributes.href.replace('\\', '/')}` };
.replace('\\', '/') return this.createHtmlTag(v);
.replace('%5C', '/');
v.attributes = { rel: 'stylesheet', type: 'text/css', href: `/${href}` };
return HtmlWebpackPluginHtmlTags.htmlTagObjectToString(v, this.options.xhtml);
}); });
const body = assetTags.bodyTags.map((v) => { const body = assetTags.body.map((v) => {
v.attributes = { src: `/${v.attributes.src}` }; v.attributes = { src: `/${v.attributes.src}` };
return HtmlWebpackPluginHtmlTags.htmlTagObjectToString(v, this.options.xhtml); return this.createHtmlTag(v);
}); });
return html return html
@@ -131,8 +125,9 @@ const config = {
use: { use: {
loader: 'worker-loader', loader: 'worker-loader',
options: { options: {
filename: '[name].js', name: '[name].js',
inline: inlineWebWorkers inline: inlineWebWorkers,
fallback: !inlineWebWorkers
} }
} }
}, },
-20
View File
@@ -1,20 +0,0 @@
{
"compilerOptions": {
"target": "es6",
"checkJs": false,
"baseUrl": "src",
"jsx": "react",
"module": "commonjs",
"moduleResolution": "node",
"paths": {
"*": [
"*"
]
}
},
"include": [
"./src/**/*"
],
"exclude": [
]
}
+4 -115
View File
@@ -1,7 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import ConfirmModal from 'Components/Modal/ConfirmModal';
import PageContent from 'Components/Page/PageContent'; import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody'; import PageContentBody from 'Components/Page/PageContentBody';
import PageToolbar from 'Components/Page/Toolbar/PageToolbar'; import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
@@ -11,84 +10,12 @@ import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody'; import TableBody from 'Components/Table/TableBody';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper'; import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import TablePager from 'Components/Table/TablePager'; import TablePager from 'Components/Table/TablePager';
import { align, icons, kinds } from 'Helpers/Props'; import { align, icons } from 'Helpers/Props';
import getRemovedItems from 'Utilities/Object/getRemovedItems';
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
import removeOldSelectedState from 'Utilities/Table/removeOldSelectedState';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
import BlacklistRowConnector from './BlacklistRowConnector'; import BlacklistRowConnector from './BlacklistRowConnector';
class Blacklist extends Component { class Blacklist extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
allSelected: false,
allUnselected: false,
lastToggled: null,
selectedState: {},
isConfirmRemoveModalOpen: false,
items: props.items
};
}
componentDidUpdate(prevProps) {
const {
items
} = this.props;
if (hasDifferentItems(prevProps.items, items)) {
this.setState((state) => {
return {
...removeOldSelectedState(state, getRemovedItems(prevProps.items, items)),
items
};
});
return;
}
}
//
// Control
getSelectedIds = () => {
return getSelectedIds(this.state.selectedState);
}
//
// Listeners
onSelectAllChange = ({ value }) => {
this.setState(selectAll(this.state.selectedState, value));
}
onSelectedChange = ({ id, value, shiftKey = false }) => {
this.setState((state) => {
return toggleSelected(state, this.props.items, id, value, shiftKey);
});
}
onRemoveSelectedPress = () => {
this.setState({ isConfirmRemoveModalOpen: true });
}
onRemoveSelectedConfirmed = () => {
this.props.onRemoveSelected(this.getSelectedIds());
this.setState({ isConfirmRemoveModalOpen: false });
}
onConfirmRemoveModalClose = () => {
this.setState({ isConfirmRemoveModalOpen: false });
}
// //
// Render // Render
@@ -100,33 +27,15 @@ class Blacklist extends Component {
items, items,
columns, columns,
totalRecords, totalRecords,
isRemoving,
isClearingBlacklistExecuting, isClearingBlacklistExecuting,
onClearBlacklistPress, onClearBlacklistPress,
...otherProps ...otherProps
} = this.props; } = this.props;
const {
allSelected,
allUnselected,
selectedState,
isConfirmRemoveModalOpen
} = this.state;
const selectedIds = this.getSelectedIds();
return ( return (
<PageContent title={translate('Blacklist')}> <PageContent title="Blacklist">
<PageToolbar> <PageToolbar>
<PageToolbarSection> <PageToolbarSection>
<PageToolbarButton
label="Remove Selected"
iconName={icons.REMOVE}
isDisabled={!selectedIds.length}
isSpinning={isRemoving}
onPress={this.onRemoveSelectedPress}
/>
<PageToolbarButton <PageToolbarButton
label={translate('Clear')} label={translate('Clear')}
iconName={icons.CLEAR} iconName={icons.CLEAR}
@@ -156,15 +65,13 @@ class Blacklist extends Component {
{ {
!isFetching && !!error && !isFetching && !!error &&
<div> <div>Unable to load blacklist</div>
{translate('UnableToLoadBlacklist')}
</div>
} }
{ {
isPopulated && !error && !items.length && isPopulated && !error && !items.length &&
<div> <div>
{translate('NoHistory')} No history blacklist
</div> </div>
} }
@@ -172,12 +79,8 @@ class Blacklist extends Component {
isPopulated && !error && !!items.length && isPopulated && !error && !!items.length &&
<div> <div>
<Table <Table
selectAll={true}
allSelected={allSelected}
allUnselected={allUnselected}
columns={columns} columns={columns}
{...otherProps} {...otherProps}
onSelectAllChange={this.onSelectAllChange}
> >
<TableBody> <TableBody>
{ {
@@ -185,10 +88,8 @@ class Blacklist extends Component {
return ( return (
<BlacklistRowConnector <BlacklistRowConnector
key={item.id} key={item.id}
isSelected={selectedState[item.id] || false}
columns={columns} columns={columns}
{...item} {...item}
onSelectedChange={this.onSelectedChange}
/> />
); );
}) })
@@ -204,16 +105,6 @@ class Blacklist extends Component {
</div> </div>
} }
</PageContentBody> </PageContentBody>
<ConfirmModal
isOpen={isConfirmRemoveModalOpen}
kind={kinds.DANGER}
title="Remove Selected"
message={'Are you sure you want to remove the selected items from the blacklist?'}
confirmLabel="Remove Selected"
onConfirm={this.onRemoveSelectedConfirmed}
onCancel={this.onConfirmRemoveModalClose}
/>
</PageContent> </PageContent>
); );
} }
@@ -226,9 +117,7 @@ Blacklist.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired,
totalRecords: PropTypes.number, totalRecords: PropTypes.number,
isRemoving: PropTypes.bool.isRequired,
isClearingBlacklistExecuting: PropTypes.bool.isRequired, isClearingBlacklistExecuting: PropTypes.bool.isRequired,
onRemoveSelected: PropTypes.func.isRequired,
onClearBlacklistPress: PropTypes.func.isRequired onClearBlacklistPress: PropTypes.func.isRequired
}; };
@@ -89,10 +89,6 @@ class BlacklistConnector extends Component {
this.props.gotoBlacklistPage({ page }); this.props.gotoBlacklistPage({ page });
} }
onRemoveSelected = (ids) => {
this.props.removeBlacklistItems({ ids });
}
onSortPress = (sortKey) => { onSortPress = (sortKey) => {
this.props.setBlacklistSort({ sortKey }); this.props.setBlacklistSort({ sortKey });
} }
@@ -128,7 +124,6 @@ class BlacklistConnector extends Component {
onNextPagePress={this.onNextPagePress} onNextPagePress={this.onNextPagePress}
onLastPagePress={this.onLastPagePress} onLastPagePress={this.onLastPagePress}
onPageSelect={this.onPageSelect} onPageSelect={this.onPageSelect}
onRemoveSelected={this.onRemoveSelected}
onSortPress={this.onSortPress} onSortPress={this.onSortPress}
onTableOptionChange={this.onTableOptionChange} onTableOptionChange={this.onTableOptionChange}
onClearBlacklistPress={this.onClearBlacklistPress} onClearBlacklistPress={this.onClearBlacklistPress}
@@ -148,7 +143,6 @@ BlacklistConnector.propTypes = {
gotoBlacklistNextPage: PropTypes.func.isRequired, gotoBlacklistNextPage: PropTypes.func.isRequired,
gotoBlacklistLastPage: PropTypes.func.isRequired, gotoBlacklistLastPage: PropTypes.func.isRequired,
gotoBlacklistPage: PropTypes.func.isRequired, gotoBlacklistPage: PropTypes.func.isRequired,
removeBlacklistItems: PropTypes.func.isRequired,
setBlacklistSort: PropTypes.func.isRequired, setBlacklistSort: PropTypes.func.isRequired,
setBlacklistTableOption: PropTypes.func.isRequired, setBlacklistTableOption: PropTypes.func.isRequired,
clearBlacklist: PropTypes.func.isRequired, clearBlacklist: PropTypes.func.isRequired,
@@ -8,7 +8,6 @@ import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent'; import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter'; import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader'; import ModalHeader from 'Components/Modal/ModalHeader';
import translate from 'Utilities/String/translate';
class BlacklistDetailsModal extends Component { class BlacklistDetailsModal extends Component {
@@ -40,19 +39,19 @@ class BlacklistDetailsModal extends Component {
<ModalBody> <ModalBody>
<DescriptionList> <DescriptionList>
<DescriptionListItem <DescriptionListItem
title={translate('Name')} title="Name"
data={sourceTitle} data={sourceTitle}
/> />
<DescriptionListItem <DescriptionListItem
title={translate('Protocol')} title="Protocol"
data={protocol} data={protocol}
/> />
{ {
!!message && !!message &&
<DescriptionListItem <DescriptionListItem
title={translate('Indexer')} title="Indexer"
data={indexer} data={indexer}
/> />
} }
@@ -60,7 +59,7 @@ class BlacklistDetailsModal extends Component {
{ {
!!message && !!message &&
<DescriptionListItem <DescriptionListItem
title={translate('Message')} title="Message"
data={message} data={message}
/> />
} }
@@ -69,7 +68,7 @@ class BlacklistDetailsModal extends Component {
<ModalFooter> <ModalFooter>
<Button onPress={onModalClose}> <Button onPress={onModalClose}>
{translate('Close')} Close
</Button> </Button>
</ModalFooter> </ModalFooter>
</ModalContent> </ModalContent>
@@ -3,14 +3,12 @@ import React, { Component } from 'react';
import IconButton from 'Components/Link/IconButton'; import IconButton from 'Components/Link/IconButton';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector'; import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import TableRow from 'Components/Table/TableRow'; import TableRow from 'Components/Table/TableRow';
import { icons, kinds } from 'Helpers/Props'; import { icons, kinds } from 'Helpers/Props';
import MovieFormats from 'Movie/MovieFormats'; import MovieFormats from 'Movie/MovieFormats';
import MovieLanguage from 'Movie/MovieLanguage'; import MovieLanguage from 'Movie/MovieLanguage';
import MovieQuality from 'Movie/MovieQuality'; import MovieQuality from 'Movie/MovieQuality';
import MovieTitleLink from 'Movie/MovieTitleLink'; import MovieTitleLink from 'Movie/MovieTitleLink';
import translate from 'Utilities/String/translate';
import BlacklistDetailsModal from './BlacklistDetailsModal'; import BlacklistDetailsModal from './BlacklistDetailsModal';
import styles from './BlacklistRow.css'; import styles from './BlacklistRow.css';
@@ -43,7 +41,6 @@ class BlacklistRow extends Component {
render() { render() {
const { const {
id,
movie, movie,
sourceTitle, sourceTitle,
quality, quality,
@@ -53,9 +50,7 @@ class BlacklistRow extends Component {
protocol, protocol,
indexer, indexer,
message, message,
isSelected,
columns, columns,
onSelectedChange,
onRemovePress onRemovePress
} = this.props; } = this.props;
@@ -65,12 +60,6 @@ class BlacklistRow extends Component {
return ( return (
<TableRow> <TableRow>
<TableSelectCell
id={id}
isSelected={isSelected}
onSelectedChange={onSelectedChange}
/>
{ {
columns.map((column) => { columns.map((column) => {
const { const {
@@ -166,7 +155,7 @@ class BlacklistRow extends Component {
/> />
<IconButton <IconButton
title={translate('RemoveFromBlacklist')} title="Remove from blacklist"
name={icons.REMOVE} name={icons.REMOVE}
kind={kinds.DANGER} kind={kinds.DANGER}
onPress={onRemovePress} onPress={onRemovePress}
@@ -204,9 +193,7 @@ BlacklistRow.propTypes = {
protocol: PropTypes.string.isRequired, protocol: PropTypes.string.isRequired,
indexer: PropTypes.string, indexer: PropTypes.string,
message: PropTypes.string, message: PropTypes.string,
isSelected: PropTypes.bool.isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired,
onSelectedChange: PropTypes.func.isRequired,
onRemovePress: PropTypes.func.isRequired onRemovePress: PropTypes.func.isRequired
}; };
@@ -1,6 +1,6 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { removeBlacklistItem } from 'Store/Actions/blacklistActions'; import { removeFromBlacklist } from 'Store/Actions/blacklistActions';
import createMovieSelector from 'Store/Selectors/createMovieSelector'; import createMovieSelector from 'Store/Selectors/createMovieSelector';
import BlacklistRow from './BlacklistRow'; import BlacklistRow from './BlacklistRow';
@@ -18,7 +18,7 @@ function createMapStateToProps() {
function createMapDispatchToProps(dispatch, props) { function createMapDispatchToProps(dispatch, props) {
return { return {
onRemovePress() { onRemovePress() {
dispatch(removeBlacklistItem({ id: props.id })); dispatch(removeFromBlacklist({ id: props.id }));
} }
}; };
} }
@@ -7,7 +7,6 @@ import DescriptionListItemTitle from 'Components/DescriptionList/DescriptionList
import Link from 'Components/Link/Link'; import Link from 'Components/Link/Link';
import formatDateTime from 'Utilities/Date/formatDateTime'; import formatDateTime from 'Utilities/Date/formatDateTime';
import formatAge from 'Utilities/Number/formatAge'; import formatAge from 'Utilities/Number/formatAge';
import translate from 'Utilities/String/translate';
import styles from './HistoryDetails.css'; import styles from './HistoryDetails.css';
function HistoryDetails(props) { function HistoryDetails(props) {
@@ -36,14 +35,14 @@ function HistoryDetails(props) {
<DescriptionList> <DescriptionList>
<DescriptionListItem <DescriptionListItem
descriptionClassName={styles.description} descriptionClassName={styles.description}
title={translate('Name')} title="Name"
data={sourceTitle} data={sourceTitle}
/> />
{ {
!!indexer && !!indexer &&
<DescriptionListItem <DescriptionListItem
title={translate('Indexer')} title="Indexer"
data={indexer} data={indexer}
/> />
} }
@@ -52,7 +51,7 @@ function HistoryDetails(props) {
!!releaseGroup && !!releaseGroup &&
<DescriptionListItem <DescriptionListItem
descriptionClassName={styles.description} descriptionClassName={styles.description}
title={translate('ReleaseGroup')} title="Release Group"
data={releaseGroup} data={releaseGroup}
/> />
} }
@@ -73,7 +72,7 @@ function HistoryDetails(props) {
{ {
!!downloadClient && !!downloadClient &&
<DescriptionListItem <DescriptionListItem
title={translate('DownloadClient')} title="Download Client"
data={downloadClient} data={downloadClient}
/> />
} }
@@ -81,7 +80,7 @@ function HistoryDetails(props) {
{ {
!!downloadId && !!downloadId &&
<DescriptionListItem <DescriptionListItem
title={translate('GrabID')} title="Grab ID"
data={downloadId} data={downloadId}
/> />
} }
@@ -89,7 +88,7 @@ function HistoryDetails(props) {
{ {
!!indexer && !!indexer &&
<DescriptionListItem <DescriptionListItem
title={translate('AgeWhenGrabbed')} title="Age (when grabbed)"
data={formatAge(age, ageHours, ageMinutes)} data={formatAge(age, ageHours, ageMinutes)}
/> />
} }
@@ -97,7 +96,7 @@ function HistoryDetails(props) {
{ {
!!publishedDate && !!publishedDate &&
<DescriptionListItem <DescriptionListItem
title={translate('PublishedDate')} title="Published Date"
data={formatDateTime(publishedDate, shortDateFormat, timeFormat, { includeSeconds: true })} data={formatDateTime(publishedDate, shortDateFormat, timeFormat, { includeSeconds: true })}
/> />
} }
@@ -114,14 +113,14 @@ function HistoryDetails(props) {
<DescriptionList> <DescriptionList>
<DescriptionListItem <DescriptionListItem
descriptionClassName={styles.description} descriptionClassName={styles.description}
title={translate('Name')} title="Name"
data={sourceTitle} data={sourceTitle}
/> />
{ {
!!message && !!message &&
<DescriptionListItem <DescriptionListItem
title={translate('Message')} title="Message"
data={message} data={message}
/> />
} }
@@ -139,7 +138,7 @@ function HistoryDetails(props) {
<DescriptionList> <DescriptionList>
<DescriptionListItem <DescriptionListItem
descriptionClassName={styles.description} descriptionClassName={styles.description}
title={translate('Name')} title="Name"
data={sourceTitle} data={sourceTitle}
/> />
@@ -147,7 +146,7 @@ function HistoryDetails(props) {
!!droppedPath && !!droppedPath &&
<DescriptionListItem <DescriptionListItem
descriptionClassName={styles.description} descriptionClassName={styles.description}
title={translate('Source')} title="Source"
data={droppedPath} data={droppedPath}
/> />
} }
@@ -156,7 +155,7 @@ function HistoryDetails(props) {
!!importedPath && !!importedPath &&
<DescriptionListItem <DescriptionListItem
descriptionClassName={styles.description} descriptionClassName={styles.description}
title={translate('ImportedTo')} title="Imported To"
data={importedPath} data={importedPath}
/> />
} }
@@ -188,12 +187,12 @@ function HistoryDetails(props) {
return ( return (
<DescriptionList> <DescriptionList>
<DescriptionListItem <DescriptionListItem
title={translate('Name')} title="Name"
data={sourceTitle} data={sourceTitle}
/> />
<DescriptionListItem <DescriptionListItem
title={translate('Reason')} title="Reason"
data={reasonMessage} data={reasonMessage}
/> />
</DescriptionList> </DescriptionList>
@@ -211,22 +210,22 @@ function HistoryDetails(props) {
return ( return (
<DescriptionList> <DescriptionList>
<DescriptionListItem <DescriptionListItem
title={translate('SourcePath')} title="Source Path"
data={sourcePath} data={sourcePath}
/> />
<DescriptionListItem <DescriptionListItem
title={translate('SourceRelativePath')} title="Source Relative Path"
data={sourceRelativePath} data={sourceRelativePath}
/> />
<DescriptionListItem <DescriptionListItem
title={translate('DestinationPath')} title="Destination Path"
data={path} data={path}
/> />
<DescriptionListItem <DescriptionListItem
title={translate('DestinationRelativePath')} title="Destination Relative Path"
data={relativePath} data={relativePath}
/> />
</DescriptionList> </DescriptionList>
@@ -242,14 +241,14 @@ function HistoryDetails(props) {
<DescriptionList> <DescriptionList>
<DescriptionListItem <DescriptionListItem
descriptionClassName={styles.description} descriptionClassName={styles.description}
title={translate('Name')} title="Name"
data={sourceTitle} data={sourceTitle}
/> />
{ {
!!message && !!message &&
<DescriptionListItem <DescriptionListItem
title={translate('Message')} title="Message"
data={message} data={message}
/> />
} }
@@ -261,7 +260,7 @@ function HistoryDetails(props) {
<DescriptionList> <DescriptionList>
<DescriptionListItem <DescriptionListItem
descriptionClassName={styles.description} descriptionClassName={styles.description}
title={translate('Name')} title="Name"
data={sourceTitle} data={sourceTitle}
/> />
</DescriptionList> </DescriptionList>
@@ -1,3 +1,4 @@
import _ from 'lodash';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector'; import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
@@ -7,10 +8,10 @@ function createMapStateToProps() {
return createSelector( return createSelector(
createUISettingsSelector(), createUISettingsSelector(),
(uiSettings) => { (uiSettings) => {
return { return _.pick(uiSettings, [
shortDateFormat: uiSettings.shortDateFormat, 'shortDateFormat',
timeFormat: uiSettings.timeFormat 'timeFormat'
}; ]);
} }
); );
} }
@@ -8,7 +8,6 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter'; import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader'; import ModalHeader from 'Components/Modal/ModalHeader';
import { kinds } from 'Helpers/Props'; import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import HistoryDetails from './HistoryDetails'; import HistoryDetails from './HistoryDetails';
import styles from './HistoryDetailsModal.css'; import styles from './HistoryDetailsModal.css';
@@ -80,7 +79,7 @@ function HistoryDetailsModal(props) {
<Button <Button
onPress={onModalClose} onPress={onModalClose}
> >
{translate('Close')} Close
</Button> </Button>
</ModalFooter> </ModalFooter>
</ModalContent> </ModalContent>
+2 -4
View File
@@ -43,7 +43,7 @@ class History extends Component {
const hasError = error || moviesError; const hasError = error || moviesError;
return ( return (
<PageContent title={translate('History')}> <PageContent title="History">
<PageToolbar> <PageToolbar>
<PageToolbarSection> <PageToolbarSection>
<PageToolbarButton <PageToolbarButton
@@ -83,9 +83,7 @@ class History extends Component {
{ {
!isFetchingAny && hasError && !isFetchingAny && hasError &&
<div> <div>Unable to load history</div>
{translate('UnableToLoadHistory')}
</div>
} }
{ {
+3 -3
View File
@@ -149,7 +149,7 @@ class Queue extends Component {
const disableSelectedActions = selectedCount === 0; const disableSelectedActions = selectedCount === 0;
return ( return (
<PageContent title={translate('Queue')}> <PageContent title="Queue">
<PageToolbar> <PageToolbar>
<PageToolbarSection> <PageToolbarSection>
<PageToolbarButton <PageToolbarButton
@@ -162,7 +162,7 @@ class Queue extends Component {
<PageToolbarSeparator /> <PageToolbarSeparator />
<PageToolbarButton <PageToolbarButton
label={translate('GrabSelected')} label="Grab Selected"
iconName={icons.DOWNLOAD} iconName={icons.DOWNLOAD}
isDisabled={disableSelectedActions || !isPendingSelected} isDisabled={disableSelectedActions || !isPendingSelected}
isSpinning={isGrabbing} isSpinning={isGrabbing}
@@ -170,7 +170,7 @@ class Queue extends Component {
/> />
<PageToolbarButton <PageToolbarButton
label={translate('RemoveSelected')} label="Remove Selected"
iconName={icons.REMOVE} iconName={icons.REMOVE}
isDisabled={disableSelectedActions} isDisabled={disableSelectedActions}
isSpinning={isRemoving} isSpinning={isRemoving}
+11 -42
View File
@@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import { icons, kinds } from 'Helpers/Props'; import { icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
function QueueDetails(props) { function QueueDetails(props) {
const { const {
@@ -11,20 +10,20 @@ function QueueDetails(props) {
size, size,
sizeleft, sizeleft,
estimatedCompletionTime, estimatedCompletionTime,
status, status: queueStatus,
trackedDownloadState,
trackedDownloadStatus,
errorMessage, errorMessage,
progressBar progressBar
} = props; } = props;
const progress = size ? (100 - sizeleft / size * 100) : 0; const status = queueStatus.toLowerCase();
const progress = (100 - sizeleft / size * 100);
if (status === 'pending') { if (status === 'pending') {
return ( return (
<Icon <Icon
name={icons.PENDING} name={icons.PENDING}
title={translate('ReleaseWillBeProcessedInterp', [moment(estimatedCompletionTime).fromNow()])} title={`Release will be processed ${moment(estimatedCompletionTime).fromNow()}`}
/> />
); );
} }
@@ -35,40 +34,12 @@ function QueueDetails(props) {
<Icon <Icon
name={icons.DOWNLOAD} name={icons.DOWNLOAD}
kind={kinds.DANGER} kind={kinds.DANGER}
title={translate('ImportFailedInterp', [errorMessage])} title={`Import failed: ${errorMessage}`}
/> />
); );
} }
if (trackedDownloadStatus === 'warning') { // TODO: show an icon when download is complete, but not imported yet?
return (
<Icon
name={icons.DOWNLOAD}
kind={kinds.WARNING}
title={'Downloaded - Unable to Import: check logs for details'}
/>
);
}
if (trackedDownloadState === 'importPending') {
return (
<Icon
name={icons.DOWNLOAD}
kind={kinds.PURPLE}
title={`${translate('Downloaded')} - ${translate('WaitingToImport')}`}
/>
);
}
if (trackedDownloadState === 'importing') {
return (
<Icon
name={icons.DOWNLOAD}
kind={kinds.PURPLE}
title={`${translate('Downloaded')} - ${translate('Importing')}`}
/>
);
}
} }
if (errorMessage) { if (errorMessage) {
@@ -76,7 +47,7 @@ function QueueDetails(props) {
<Icon <Icon
name={icons.DOWNLOADING} name={icons.DOWNLOADING}
kind={kinds.DANGER} kind={kinds.DANGER}
title={translate('DownloadFailedInterp', [errorMessage])} title={`Download failed: ${errorMessage}`}
/> />
); );
} }
@@ -86,7 +57,7 @@ function QueueDetails(props) {
<Icon <Icon
name={icons.DOWNLOADING} name={icons.DOWNLOADING}
kind={kinds.DANGER} kind={kinds.DANGER}
title={translate('DownloadFailedCheckDownloadClientForMoreDetails')} title="Download failed: check download client for more details"
/> />
); );
} }
@@ -96,7 +67,7 @@ function QueueDetails(props) {
<Icon <Icon
name={icons.DOWNLOADING} name={icons.DOWNLOADING}
kind={kinds.WARNING} kind={kinds.WARNING}
title={translate('DownloadWarningCheckDownloadClientForMoreDetails')} title="Download warning: check download client for more details"
/> />
); );
} }
@@ -105,7 +76,7 @@ function QueueDetails(props) {
return ( return (
<Icon <Icon
name={icons.DOWNLOADING} name={icons.DOWNLOADING}
title={translate('MovieIsDownloadingInterp', [progress.toFixed(1), title])} title={`Movie is downloading - ${progress.toFixed(1)}% ${title}`}
/> />
); );
} }
@@ -119,8 +90,6 @@ QueueDetails.propTypes = {
sizeleft: PropTypes.number.isRequired, sizeleft: PropTypes.number.isRequired,
estimatedCompletionTime: PropTypes.string, estimatedCompletionTime: PropTypes.string,
status: PropTypes.string.isRequired, status: PropTypes.string.isRequired,
trackedDownloadState: PropTypes.string.isRequired,
trackedDownloadStatus: PropTypes.string.isRequired,
errorMessage: PropTypes.string, errorMessage: PropTypes.string,
progressBar: PropTypes.node.isRequired progressBar: PropTypes.node.isRequired
}; };
+2 -3
View File
@@ -4,7 +4,6 @@ import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup'; import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel'; import FormLabel from 'Components/Form/FormLabel';
import { inputTypes } from 'Helpers/Props'; import { inputTypes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
class QueueOptions extends Component { class QueueOptions extends Component {
@@ -55,13 +54,13 @@ class QueueOptions extends Component {
return ( return (
<Fragment> <Fragment>
<FormGroup> <FormGroup>
<FormLabel>{translate('ShowUnknownMovieItems')}</FormLabel> <FormLabel>Show Unknown Movie Items</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="includeUnknownMovieItems" name="includeUnknownMovieItems"
value={includeUnknownMovieItems} value={includeUnknownMovieItems}
helpText={translate('IncludeUnknownMovieItemsHelpText')} helpText="Show items without a movie in the queue, this could include removed movie, movies or anything else in Radarr's category"
onChange={this.onOptionChange} onChange={this.onOptionChange}
/> />
</FormGroup> </FormGroup>
+1 -2
View File
@@ -15,7 +15,6 @@ import MovieLanguage from 'Movie/MovieLanguage';
import MovieQuality from 'Movie/MovieQuality'; import MovieQuality from 'Movie/MovieQuality';
import MovieTitleLink from 'Movie/MovieTitleLink'; import MovieTitleLink from 'Movie/MovieTitleLink';
import formatBytes from 'Utilities/Number/formatBytes'; import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
import QueueStatusCell from './QueueStatusCell'; import QueueStatusCell from './QueueStatusCell';
import RemoveQueueItemModal from './RemoveQueueItemModal'; import RemoveQueueItemModal from './RemoveQueueItemModal';
import TimeleftCell from './TimeleftCell'; import TimeleftCell from './TimeleftCell';
@@ -295,7 +294,7 @@ class QueueRow extends Component {
} }
<SpinnerIconButton <SpinnerIconButton
title={translate('RemoveFromQueue')} title="Remove from queue"
name={icons.REMOVE} name={icons.REMOVE}
isSpinning={isRemoving} isSpinning={isRemoving}
onPress={this.onRemoveQueueItemPress} onPress={this.onRemoveQueueItemPress}
+15 -17
View File
@@ -4,7 +4,6 @@ import Icon from 'Components/Icon';
import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRowCell from 'Components/Table/Cells/TableRowCell';
import Popover from 'Components/Tooltip/Popover'; import Popover from 'Components/Tooltip/Popover';
import { icons, kinds, tooltipPositions } from 'Helpers/Props'; import { icons, kinds, tooltipPositions } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './QueueStatusCell.css'; import styles from './QueueStatusCell.css';
function getDetailedPopoverBody(statusMessages) { function getDetailedPopoverBody(statusMessages) {
@@ -50,34 +49,34 @@ function QueueStatusCell(props) {
// status === 'downloading' // status === 'downloading'
let iconName = icons.DOWNLOADING; let iconName = icons.DOWNLOADING;
let iconKind = kinds.DEFAULT; let iconKind = kinds.DEFAULT;
let title = translate('Downloading'); let title = 'Downloading';
if (status === 'paused') { if (status === 'paused') {
iconName = icons.PAUSED; iconName = icons.PAUSED;
title = translate('Paused'); title = 'Paused';
} }
if (status === 'queued') { if (status === 'queued') {
iconName = icons.QUEUED; iconName = icons.QUEUED;
title = translate('Queued'); title = 'Queued';
} }
if (status === 'completed') { if (status === 'completed') {
iconName = icons.DOWNLOADED; iconName = icons.DOWNLOADED;
title = translate('Downloaded'); title = 'Downloaded';
if (trackedDownloadState === 'importPending') { if (trackedDownloadState === 'importPending') {
title += ` - ${translate('WaitingToImport')}`; title += ' - Waiting to Import';
iconKind = kinds.PURPLE; iconKind = kinds.PURPLE;
} }
if (trackedDownloadState === 'importing') { if (trackedDownloadState === 'importing') {
title += ` - ${translate('Importing')}`; title += ' - Importing';
iconKind = kinds.PURPLE; iconKind = kinds.PURPLE;
} }
if (trackedDownloadState === 'failedPending') { if (trackedDownloadState === 'failedPending') {
title += ` - ${translate('WaitingToProcess')}`; title += ' - Waiting to Process';
iconKind = kinds.DANGER; iconKind = kinds.DANGER;
} }
} }
@@ -88,37 +87,36 @@ function QueueStatusCell(props) {
if (status === 'delay') { if (status === 'delay') {
iconName = icons.PENDING; iconName = icons.PENDING;
title = translate('Pending'); title = 'Pending';
} }
if (status === 'DownloadClientUnavailable') { if (status === 'DownloadClientUnavailable') {
iconName = icons.PENDING; iconName = icons.PENDING;
iconKind = kinds.WARNING; iconKind = kinds.WARNING;
title = `${translate('Pending')} - ${translate('DownloadClientUnavailable')}`; title = 'Pending - Download client is unavailable';
} }
if (status === 'failed') { if (status === 'failed') {
iconName = icons.DOWNLOADING; iconName = icons.DOWNLOADING;
iconKind = kinds.DANGER; iconKind = kinds.DANGER;
title = translate('DownloadFailed'); title = 'Download failed';
} }
if (status === 'warning') { if (status === 'warning') {
iconName = icons.DOWNLOADING; iconName = icons.DOWNLOADING;
iconKind = kinds.WARNING; iconKind = kinds.WARNING;
const warningMessage = errorMessage || translate('CheckDownloadClientForDetails'); title = `Download warning: ${errorMessage || 'check download client for more details'}`;
title = translate('DownloadWarning', [warningMessage]);
} }
if (hasError) { if (hasError) {
if (status === 'completed') { if (status === 'completed') {
iconName = icons.DOWNLOAD; iconName = icons.DOWNLOAD;
iconKind = kinds.DANGER; iconKind = kinds.DANGER;
title = translate('ImportFailed', [sourceTitle]); title = `Import failed: ${sourceTitle}`;
} else { } else {
iconName = icons.DOWNLOADING; iconName = icons.DOWNLOADING;
iconKind = kinds.DANGER; iconKind = kinds.DANGER;
title = translate('DownloadFailed'); title = 'Download failed';
} }
} }
@@ -150,8 +148,8 @@ QueueStatusCell.propTypes = {
}; };
QueueStatusCell.defaultProps = { QueueStatusCell.defaultProps = {
trackedDownloadStatus: translate('Ok'), trackedDownloadStatus: 'Ok',
trackedDownloadState: translate('Downloading') trackedDownloadState: 'Downloading'
}; };
export default QueueStatusCell; export default QueueStatusCell;
@@ -10,7 +10,6 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter'; import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader'; import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, sizes } from 'Helpers/Props'; import { inputTypes, kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
class RemoveQueueItemModal extends Component { class RemoveQueueItemModal extends Component {
@@ -90,25 +89,25 @@ class RemoveQueueItemModal extends Component {
</div> </div>
<FormGroup> <FormGroup>
<FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel> <FormLabel>Remove From Download Client</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="remove" name="remove"
value={remove} value={remove}
helpTextWarning={translate('RemoveHelpTextWarning')} helpTextWarning="Removing will remove the download and the file(s) from the download client."
isDisabled={!canIgnore} isDisabled={!canIgnore}
onChange={this.onRemoveChange} onChange={this.onRemoveChange}
/> />
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<FormLabel>{translate('BlacklistRelease')}</FormLabel> <FormLabel>Blacklist Release</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="blacklist" name="blacklist"
value={blacklist} value={blacklist}
helpText={translate('BlacklistHelpText')} helpText="Starts a search for this movie again and prevents this release from being grabbed again"
onChange={this.onBlacklistChange} onChange={this.onBlacklistChange}
/> />
</FormGroup> </FormGroup>
@@ -117,7 +116,7 @@ class RemoveQueueItemModal extends Component {
<ModalFooter> <ModalFooter>
<Button onPress={this.onModalClose}> <Button onPress={this.onModalClose}>
{translate('Close')} Close
</Button> </Button>
<Button <Button
@@ -10,7 +10,6 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter'; import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader'; import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds, sizes } from 'Helpers/Props'; import { inputTypes, kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './RemoveQueueItemsModal.css'; import styles from './RemoveQueueItemsModal.css';
class RemoveQueueItemsModal extends Component { class RemoveQueueItemsModal extends Component {
@@ -91,13 +90,13 @@ class RemoveQueueItemsModal extends Component {
</div> </div>
<FormGroup> <FormGroup>
<FormLabel>{translate('RemoveFromDownloadClient')}</FormLabel> <FormLabel>Remove From Download Client</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="remove" name="remove"
value={remove} value={remove}
helpTextWarning={translate('RemoveHelpTextWarning')} helpTextWarning="Removing will remove the download and the file(s) from the download client."
isDisabled={!canIgnore} isDisabled={!canIgnore}
onChange={this.onRemoveChange} onChange={this.onRemoveChange}
/> />
@@ -112,7 +111,7 @@ class RemoveQueueItemsModal extends Component {
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="blacklist" name="blacklist"
value={blacklist} value={blacklist}
helpText={translate('BlacklistHelpText')} helpText="Prevents Radarr from automatically grabbing this movie again"
onChange={this.onBlacklistChange} onChange={this.onBlacklistChange}
/> />
</FormGroup> </FormGroup>
@@ -121,7 +120,7 @@ class RemoveQueueItemsModal extends Component {
<ModalFooter> <ModalFooter>
<Button onPress={this.onModalClose}> <Button onPress={this.onModalClose}>
{translate('Close')} Close
</Button> </Button>
<Button <Button
+2 -3
View File
@@ -5,7 +5,6 @@ import formatTime from 'Utilities/Date/formatTime';
import formatTimeSpan from 'Utilities/Date/formatTimeSpan'; import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
import getRelativeDate from 'Utilities/Date/getRelativeDate'; import getRelativeDate from 'Utilities/Date/getRelativeDate';
import formatBytes from 'Utilities/Number/formatBytes'; import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
import styles from './TimeleftCell.css'; import styles from './TimeleftCell.css';
function TimeleftCell(props) { function TimeleftCell(props) {
@@ -27,7 +26,7 @@ function TimeleftCell(props) {
return ( return (
<TableRowCell <TableRowCell
className={styles.timeleft} className={styles.timeleft}
title={translate('DelayingDownloadUntilInterp', [date, time])} title={`Delaying download until ${date} at ${time}`}
> >
- -
</TableRowCell> </TableRowCell>
@@ -41,7 +40,7 @@ function TimeleftCell(props) {
return ( return (
<TableRowCell <TableRowCell
className={styles.timeleft} className={styles.timeleft}
title={translate('RetryingDownloadInterp', [date, time])} title={`Retrying download ${date} at ${time}`}
> >
- -
</TableRowCell> </TableRowCell>
@@ -88,7 +88,7 @@ class AddNewMovie extends Component {
const isFetching = this.state.isFetching; const isFetching = this.state.isFetching;
return ( return (
<PageContent title={translate('AddNewMovie')}> <PageContent title="Add New Movie">
<PageContentBody> <PageContentBody>
<div className={styles.searchContainer}> <div className={styles.searchContainer}>
<div className={styles.searchIconContainer}> <div className={styles.searchIconContainer}>
@@ -127,7 +127,7 @@ class AddNewMovie extends Component {
!isFetching && !!error ? !isFetching && !!error ?
<div className={styles.message}> <div className={styles.message}>
<div className={styles.helpText}> <div className={styles.helpText}>
{translate('FailedLoadingSearchResults')} Failed to load search results, please try again.
</div> </div>
<div>{getErrorMessage(error)}</div> <div>{getErrorMessage(error)}</div>
</div> : null </div> : null
@@ -152,15 +152,11 @@ class AddNewMovie extends Component {
{ {
!isFetching && !error && !items.length && !!term && !isFetching && !error && !items.length && !!term &&
<div className={styles.message}> <div className={styles.message}>
<div className={styles.noResults}> <div className={styles.noResults}>Couldn't find any results for '{term}'</div>
{translate('CouldNotFindResults', [term])} <div>You can also search using TMDB ID or IMDB ID of a movie. eg. tmdb:71663</div>
</div>
<div>
{translate('YouCanAlsoSearch')}
</div>
<div> <div>
<Link to="https://github.com/Radarr/Radarr/wiki/FAQ#why-cant-i-add-a-new-movie-when-i-know-the-tmdb-id"> <Link to="https://github.com/Radarr/Radarr/wiki/FAQ#why-cant-i-add-a-new-movie-when-i-know-the-tmdb-id">
{translate('CantFindMovie')} Why can't I find my movie?
</Link> </Link>
</div> </div>
</div> </div>
@@ -173,9 +169,7 @@ class AddNewMovie extends Component {
<div className={styles.helpText}> <div className={styles.helpText}>
{translate('AddNewMessage')} {translate('AddNewMessage')}
</div> </div>
<div> <div>{translate('AddNewTmdbIdMessage')}</div>
{translate('AddNewTmdbIdMessage')}
</div>
</div> </div>
} }
@@ -183,14 +177,14 @@ class AddNewMovie extends Component {
!term && !hasExistingMovies ? !term && !hasExistingMovies ?
<div className={styles.message}> <div className={styles.message}>
<div className={styles.noMoviesText}> <div className={styles.noMoviesText}>
{translate('HaveNotAddedMovies')} You haven't added any movies yet, do you want to import some or all of your movies first?
</div> </div>
<div> <div>
<Button <Button
to="/add/import" to="/add/import"
kind={kinds.PRIMARY} kind={kinds.PRIMARY}
> >
{translate('ImportExistingMovies')} Import Existing Movies
</Button> </Button>
</div> </div>
</div> : </div> :
@@ -4,7 +4,7 @@ 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 { fetchRootFolders } from 'Store/Actions/rootFolderActions'; import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
import { fetchImportExclusions } from 'Store/Actions/Settings/importExclusions'; import { fetchNetImportExclusions } from 'Store/Actions/Settings/netImportExclusions';
import parseUrl from 'Utilities/String/parseUrl'; import parseUrl from 'Utilities/String/parseUrl';
import AddNewMovie from './AddNewMovie'; import AddNewMovie from './AddNewMovie';
@@ -29,7 +29,7 @@ const mapDispatchToProps = {
lookupMovie, lookupMovie,
clearAddMovie, clearAddMovie,
fetchRootFolders, fetchRootFolders,
fetchImportExclusions fetchNetImportExclusions
}; };
class AddNewMovieConnector extends Component { class AddNewMovieConnector extends Component {
@@ -45,7 +45,7 @@ class AddNewMovieConnector extends Component {
componentDidMount() { componentDidMount() {
this.props.fetchRootFolders(); this.props.fetchRootFolders();
this.props.fetchImportExclusions(); this.props.fetchNetImportExclusions();
} }
componentWillUnmount() { componentWillUnmount() {
@@ -102,7 +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 fetchNetImportExclusions: PropTypes.func.isRequired
}; };
export default connect(createMapStateToProps, mapDispatchToProps)(AddNewMovieConnector); export default connect(createMapStateToProps, mapDispatchToProps)(AddNewMovieConnector);
@@ -12,7 +12,6 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader'; import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes, kinds } from 'Helpers/Props'; import { inputTypes, kinds } from 'Helpers/Props';
import MoviePoster from 'Movie/MoviePoster'; import MoviePoster from 'Movie/MoviePoster';
import translate from 'Utilities/String/translate';
import styles from './AddNewMovieModalContent.css'; import styles from './AddNewMovieModalContent.css';
class AddNewMovieModalContent extends Component { class AddNewMovieModalContent extends Component {
@@ -96,7 +95,7 @@ class AddNewMovieModalContent extends Component {
<Form> <Form>
<FormGroup> <FormGroup>
<FormLabel>{translate('RootFolder')}</FormLabel> <FormLabel>Root Folder</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.ROOT_FOLDER_SELECT} type={inputTypes.ROOT_FOLDER_SELECT}
@@ -109,7 +108,7 @@ class AddNewMovieModalContent extends Component {
movieFolder: folder, movieFolder: folder,
isWindows isWindows
}} }}
helpText={translate('SubfolderWillBeCreatedAutomaticallyInterp', [folder])} helpText={`'${folder}' subfolder will be created automatically`}
onChange={onInputChange} onChange={onInputChange}
{...rootFolderPath} {...rootFolderPath}
/> />
@@ -117,7 +116,7 @@ class AddNewMovieModalContent extends Component {
<FormGroup> <FormGroup>
<FormLabel> <FormLabel>
{translate('Monitor')} Monitor
</FormLabel> </FormLabel>
<FormInputGroup <FormInputGroup
@@ -129,7 +128,7 @@ class AddNewMovieModalContent extends Component {
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<FormLabel>{translate('MinimumAvailability')}</FormLabel> <FormLabel>Minimum Availability</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.AVAILABILITY_SELECT} type={inputTypes.AVAILABILITY_SELECT}
@@ -140,7 +139,7 @@ class AddNewMovieModalContent extends Component {
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<FormLabel>{translate('QualityProfile')}</FormLabel> <FormLabel>Quality Profile</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.QUALITY_PROFILE_SELECT} type={inputTypes.QUALITY_PROFILE_SELECT}
@@ -151,7 +150,7 @@ class AddNewMovieModalContent extends Component {
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<FormLabel>{translate('Tags')}</FormLabel> <FormLabel>Tags</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.TAG} type={inputTypes.TAG}
@@ -168,7 +167,7 @@ class AddNewMovieModalContent extends Component {
<ModalFooter className={styles.modalFooter}> <ModalFooter className={styles.modalFooter}>
<label className={styles.searchForMissingMovieLabelContainer}> <label className={styles.searchForMissingMovieLabelContainer}>
<span className={styles.searchForMissingMovieLabel}> <span className={styles.searchForMissingMovieLabel}>
{translate('StartSearchForMissingMovie')} Start search for missing movie
</span> </span>
<CheckInput <CheckInput
@@ -186,7 +185,7 @@ class AddNewMovieModalContent extends Component {
isSpinning={isAdding} isSpinning={isAdding}
onPress={this.onAddMoviePress} onPress={this.onAddMoviePress}
> >
{translate('AddMovie')} Add {title}
</SpinnerButton> </SpinnerButton>
</ModalFooter> </ModalFooter>
</ModalContent> </ModalContent>
@@ -34,20 +34,10 @@
.content { .content {
flex: 0 1 100%; flex: 0 1 100%;
overflow: hidden;
}
.titleRow {
display: flex;
}
.titleContainer {
display: flex;
align-items: flex-end;
flex: 0 1 auto;
} }
.title { .title {
display: flex;
font-weight: 300; font-weight: 300;
font-size: 36px; font-size: 36px;
} }
@@ -57,12 +47,10 @@
color: $disabledColor; color: $disabledColor;
} }
.icons { .externalLink {
display: flex; margin-top: 5px;
align-items: center; margin-left: auto;
justify-content: space-between; color: $textColor;
flex: 1 0 auto;
height: 55px;
} }
.alreadyExistsIcon { .alreadyExistsIcon {
@@ -80,15 +68,3 @@
.overview { .overview {
margin-top: 20px; margin-top: 20px;
} }
.links {
margin-left: 8px;
pointer-events: all;
}
@media only screen and (max-width: $breakpointMedium) {
.titleRow {
justify-content: space-between;
overflow: hidden;
}
}
@@ -4,11 +4,8 @@ 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 Tooltip from 'Components/Tooltip/Tooltip'; import { icons, kinds, sizes } from 'Helpers/Props';
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
import MoviePoster from 'Movie/MoviePoster'; import MoviePoster from 'Movie/MoviePoster';
import translate from 'Utilities/String/translate';
import AddNewMovieModal from './AddNewMovieModal'; import AddNewMovieModal from './AddNewMovieModal';
import styles from './AddNewMovieSearchResult.css'; import styles from './AddNewMovieSearchResult.css';
@@ -53,7 +50,6 @@ class AddNewMovieSearchResult extends Component {
const { const {
tmdbId, tmdbId,
imdbId, imdbId,
youTubeTrailerId,
title, title,
titleSlug, titleSlug,
year, year,
@@ -94,43 +90,69 @@ class AddNewMovieSearchResult extends Component {
} }
<div className={styles.content}> <div className={styles.content}>
<div className={styles.titleRow}> <div className={styles.title}>
<div className={styles.titleContainer}> {title}
<div className={styles.title}>
{title}
{ {
!title.contains(year) && !!year ? !title.contains(year) && !!year &&
<span className={styles.year}> <span className={styles.year}>({year})</span>
({year}) }
</span> :
null
}
</div>
</div>
<div className={styles.icons}> {
isExistingMovie &&
<Icon
className={styles.alreadyExistsIcon}
name={icons.CHECK_CIRCLE}
size={36}
title="Already in your library"
/>
}
{ {
isExistingMovie && isExclusionMovie &&
<Icon <Icon
className={styles.alreadyExistsIcon} className={styles.exclusionIcon}
name={icons.CHECK_CIRCLE} name={icons.DANGER}
size={36} size={36}
title={translate('AlreadyInYourLibrary')} title="Movie is on Net Import Exclusion List"
/> />
} }
{ {
isExclusionMovie && isSmallScreen ?
<Icon null :
className={styles.exclusionIcon} <div className={styles.externalLink}>
name={icons.DANGER} <Link
size={36} to={`https://www.themoviedb.org/movie/${tmdbId}`}
title={translate('MovieIsOnImportExclusionList')} onPress={this.onExternalLinkPress}
/> >
} <Label size={sizes.LARGE}>
</div> TMDb
</Label>
</Link>
{
imdbId &&
<Link
to={`https://www.imdb.com/title/${imdbId}`}
onPress={this.onExternalLinkPress}
>
<Label size={sizes.LARGE}>
IMDb
</Label>
</Link>
}
<Link
to={`https://trakt.tv/search/tmdb/${tmdbId}?id_type=movie`}
onPress={this.onExternalLinkPress}
>
<Label size={sizes.LARGE}>
Trakt
</Label>
</Link>
</div>
}
</div> </div>
<div> <div>
@@ -148,33 +170,6 @@ class AddNewMovieSearchResult extends Component {
</Label> </Label>
} }
<Tooltip
anchor={
<Label
size={sizes.LARGE}
>
<Icon
name={icons.EXTERNAL_LINK}
size={13}
/>
<span className={styles.links}>
Links
</span>
</Label>
}
tooltip={
<MovieDetailsLinks
tmdbId={tmdbId}
youTubeTrailerId={youTubeTrailerId}
imdbId={imdbId}
/>
}
canFlip={true}
kind={kinds.INVERSE}
position={tooltipPositions.BOTTOM}
/>
{ {
status === 'ended' && status === 'ended' &&
<Label <Label
@@ -186,6 +181,42 @@ class AddNewMovieSearchResult extends Component {
} }
</div> </div>
{
isSmallScreen ?
<div className={styles.externalLink}>
<Link
to={`https://www.themoviedb.org/movie/${tmdbId}`}
onPress={this.onExternalLinkPress}
>
<Label size={sizes.LARGE}>
TMDb
</Label>
</Link>
{
imdbId &&
<Link
to={`https://www.imdb.com/title/${imdbId}`}
onPress={this.onExternalLinkPress}
>
<Label size={sizes.LARGE}>
IMDb
</Label>
</Link>
}
<Link
to={`https://trakt.tv/search/tmdb/${tmdbId}?id_type=movie`}
onPress={this.onExternalLinkPress}
>
<Label size={sizes.LARGE}>
Trakt
</Label>
</Link>
</div> :
null
}
<div className={styles.overview}> <div className={styles.overview}>
{overview} {overview}
</div> </div>
@@ -210,7 +241,6 @@ class AddNewMovieSearchResult extends Component {
AddNewMovieSearchResult.propTypes = { AddNewMovieSearchResult.propTypes = {
tmdbId: PropTypes.number.isRequired, tmdbId: PropTypes.number.isRequired,
imdbId: PropTypes.string, imdbId: PropTypes.string,
youTubeTrailerId: PropTypes.string,
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
titleSlug: PropTypes.string.isRequired, titleSlug: PropTypes.string.isRequired,
year: PropTypes.number.isRequired, year: PropTypes.number.isRequired,
@@ -3,7 +3,6 @@ import React, { Component } from 'react';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent'; import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody'; import PageContentBody from 'Components/Page/PageContentBody';
import translate from 'Utilities/String/translate';
import getSelectedIds from 'Utilities/Table/getSelectedIds'; import getSelectedIds from 'Utilities/Table/getSelectedIds';
import selectAll from 'Utilities/Table/selectAll'; import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected'; import toggleSelected from 'Utilities/Table/toggleSelected';
@@ -81,7 +80,6 @@ class ImportMovie extends Component {
path, path,
rootFoldersFetching, rootFoldersFetching,
rootFoldersError, rootFoldersError,
rootFoldersPopulated,
unmappedFolders unmappedFolders
} = this.props; } = this.props;
@@ -93,7 +91,7 @@ class ImportMovie extends Component {
} = this.state; } = this.state;
return ( return (
<PageContent title={translate('ImportMovies')}> <PageContent title="Import Movies">
<PageContentBody <PageContentBody
registerScroller={this.setScrollerRef} registerScroller={this.setScrollerRef}
onScroll={this.onScroll} onScroll={this.onScroll}
@@ -104,16 +102,13 @@ class ImportMovie extends Component {
{ {
!rootFoldersFetching && !!rootFoldersError ? !rootFoldersFetching && !!rootFoldersError ?
<div> <div>Unable to load root folders</div> :
{translate('UnableToLoadRootFolders')}
</div> :
null null
} }
{ {
!rootFoldersError && !rootFoldersError &&
!rootFoldersFetching && !rootFoldersFetching &&
rootFoldersPopulated &&
!unmappedFolders.length ? !unmappedFolders.length ?
<div> <div>
All movies in {path} have been imported All movies in {path} have been imported
@@ -124,7 +119,6 @@ class ImportMovie extends Component {
{ {
!rootFoldersError && !rootFoldersError &&
!rootFoldersFetching && !rootFoldersFetching &&
rootFoldersPopulated &&
!!unmappedFolders.length && !!unmappedFolders.length &&
scroller ? scroller ?
<ImportMovieTableConnector <ImportMovieTableConnector
@@ -31,7 +31,3 @@
margin: 0 10px 0 12px; margin: 0 10px 0 12px;
text-align: left; text-align: left;
} }
.importError {
margin-left: 10px;
}
@@ -3,14 +3,11 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
// import CheckInput from 'Components/Form/CheckInput'; // import CheckInput from 'Components/Form/CheckInput';
import FormInputGroup from 'Components/Form/FormInputGroup'; import FormInputGroup from 'Components/Form/FormInputGroup';
import Icon from 'Components/Icon';
import Button from 'Components/Link/Button'; import Button from 'Components/Link/Button';
import SpinnerButton from 'Components/Link/SpinnerButton'; import SpinnerButton from 'Components/Link/SpinnerButton';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContentFooter from 'Components/Page/PageContentFooter'; import PageContentFooter from 'Components/Page/PageContentFooter';
import Popover from 'Components/Tooltip/Popover'; import { inputTypes, kinds } from 'Helpers/Props';
import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './ImportMovieFooter.css'; import styles from './ImportMovieFooter.css';
const MIXED = 'mixed'; const MIXED = 'mixed';
@@ -96,10 +93,7 @@ class ImportMovieFooter extends Component {
isMonitorMixed, isMonitorMixed,
isQualityProfileIdMixed, isQualityProfileIdMixed,
isMinimumAvailabilityMixed, isMinimumAvailabilityMixed,
hasUnsearchedItems,
importError,
onImportPress, onImportPress,
onLookupPress,
onCancelLookupPress onCancelLookupPress
} = this.props; } = this.props;
@@ -113,7 +107,7 @@ class ImportMovieFooter extends Component {
<PageContentFooter> <PageContentFooter>
<div className={styles.inputContainer}> <div className={styles.inputContainer}>
<div className={styles.label}> <div className={styles.label}>
{translate('Monitor')} Monitor
</div> </div>
<FormInputGroup <FormInputGroup
@@ -128,7 +122,7 @@ class ImportMovieFooter extends Component {
<div className={styles.inputContainer}> <div className={styles.inputContainer}>
<div className={styles.label}> <div className={styles.label}>
{translate('MinimumAvailability')} Minimum Availability
</div> </div>
<FormInputGroup <FormInputGroup
@@ -143,7 +137,7 @@ class ImportMovieFooter extends Component {
<div className={styles.inputContainer}> <div className={styles.inputContainer}>
<div className={styles.label}> <div className={styles.label}>
{translate('QualityProfile')} Quality Profile
</div> </div>
<FormInputGroup <FormInputGroup
@@ -169,75 +163,31 @@ class ImportMovieFooter extends Component {
isDisabled={!selectedCount || isLookingUpMovie} isDisabled={!selectedCount || isLookingUpMovie}
onPress={onImportPress} onPress={onImportPress}
> >
{translate('Import')} {selectedCount} {selectedCount > 1 ? translate('Movies') : translate('Movie')} Import {selectedCount} {selectedCount > 1 ? 'Movies' : 'Movie'}
</SpinnerButton> </SpinnerButton>
{ {
isLookingUpMovie ? isLookingUpMovie &&
<Button <Button
className={styles.loadingButton} className={styles.loadingButton}
kind={kinds.WARNING} kind={kinds.WARNING}
onPress={onCancelLookupPress} onPress={onCancelLookupPress}
> >
{translate('CancelProcessing')} Cancel Processing
</Button> : </Button>
null
} }
{ {
hasUnsearchedItems ? isLookingUpMovie &&
<Button
className={styles.loadingButton}
kind={kinds.SUCCESS}
onPress={onLookupPress}
>
{translate('StartProcessing')}
</Button> :
null
}
{
isLookingUpMovie ?
<LoadingIndicator <LoadingIndicator
className={styles.loading} className={styles.loading}
size={24} size={24}
/> : />
null
} }
{ {
isLookingUpMovie ? isLookingUpMovie &&
translate('ProcessingFolders') : 'Processing Folders'
null
}
{
importError ?
<Popover
anchor={
<Icon
className={styles.importError}
name={icons.WARNING}
kind={kinds.WARNING}
/>
}
title={translate('ImportErrors')}
body={
<ul>
{
importError.responseJSON.map((error, index) => {
return (
<li key={index}>
{error.errorMessage}
</li>
);
})
}
</ul>
}
position={tooltipPositions.RIGHT}
/> :
null
} }
</div> </div>
</div> </div>
@@ -256,11 +206,8 @@ ImportMovieFooter.propTypes = {
isMonitorMixed: PropTypes.bool.isRequired, isMonitorMixed: PropTypes.bool.isRequired,
isQualityProfileIdMixed: PropTypes.bool.isRequired, isQualityProfileIdMixed: PropTypes.bool.isRequired,
isMinimumAvailabilityMixed: PropTypes.bool.isRequired, isMinimumAvailabilityMixed: PropTypes.bool.isRequired,
hasUnsearchedItems: PropTypes.bool.isRequired,
importError: PropTypes.object,
onInputChange: PropTypes.func.isRequired, onInputChange: PropTypes.func.isRequired,
onImportPress: PropTypes.func.isRequired, onImportPress: PropTypes.func.isRequired,
onLookupPress: PropTypes.func.isRequired,
onCancelLookupPress: PropTypes.func.isRequired onCancelLookupPress: PropTypes.func.isRequired
}; };
@@ -1,7 +1,7 @@
import _ from 'lodash'; import _ from 'lodash';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { cancelLookupMovie, lookupUnsearchedMovies } from 'Store/Actions/importMovieActions'; import { cancelLookupMovie } from 'Store/Actions/importMovieActions';
import ImportMovieFooter from './ImportMovieFooter'; import ImportMovieFooter from './ImportMovieFooter';
function isMixed(items, selectedIds, defaultValue, key) { function isMixed(items, selectedIds, defaultValue, key) {
@@ -25,14 +25,12 @@ function createMapStateToProps() {
const { const {
isLookingUpMovie, isLookingUpMovie,
isImporting, isImporting,
items, items
importError
} = importMovie; } = importMovie;
const isMonitorMixed = isMixed(items, selectedIds, defaultMonitor, 'monitor'); const isMonitorMixed = isMixed(items, selectedIds, defaultMonitor, 'monitor');
const isQualityProfileIdMixed = isMixed(items, selectedIds, defaultQualityProfileId, 'qualityProfileId'); const isQualityProfileIdMixed = isMixed(items, selectedIds, defaultQualityProfileId, 'qualityProfileId');
const isMinimumAvailabilityMixed = isMixed(items, selectedIds, defaultMinimumAvailability, 'minimumAvailability'); const isMinimumAvailabilityMixed = isMixed(items, selectedIds, defaultMinimumAvailability, 'minimumAvailability');
const hasUnsearchedItems = !isLookingUpMovie && items.some((item) => !item.isPopulated);
return { return {
selectedCount: selectedIds.length, selectedCount: selectedIds.length,
@@ -43,16 +41,13 @@ function createMapStateToProps() {
defaultMinimumAvailability, defaultMinimumAvailability,
isMonitorMixed, isMonitorMixed,
isQualityProfileIdMixed, isQualityProfileIdMixed,
isMinimumAvailabilityMixed, isMinimumAvailabilityMixed
importError,
hasUnsearchedItems
}; };
} }
); );
} }
const mapDispatchToProps = { const mapDispatchToProps = {
onLookupPress: lookupUnsearchedMovies,
onCancelLookupPress: cancelLookupMovie onCancelLookupPress: cancelLookupMovie
}; };
@@ -3,7 +3,6 @@ import React from 'react';
import VirtualTableHeader from 'Components/Table/VirtualTableHeader'; import VirtualTableHeader from 'Components/Table/VirtualTableHeader';
import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell'; import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell';
import VirtualTableSelectAllHeaderCell from 'Components/Table/VirtualTableSelectAllHeaderCell'; import VirtualTableSelectAllHeaderCell from 'Components/Table/VirtualTableSelectAllHeaderCell';
import translate from 'Utilities/String/translate';
import styles from './ImportMovieHeader.css'; import styles from './ImportMovieHeader.css';
function ImportMovieHeader(props) { function ImportMovieHeader(props) {
@@ -25,35 +24,35 @@ function ImportMovieHeader(props) {
className={styles.folder} className={styles.folder}
name="folder" name="folder"
> >
{translate('Folder')} Folder
</VirtualTableHeaderCell> </VirtualTableHeaderCell>
<VirtualTableHeaderCell <VirtualTableHeaderCell
className={styles.monitor} className={styles.monitor}
name="monitor" name="monitor"
> >
{translate('Monitor')} Monitor
</VirtualTableHeaderCell> </VirtualTableHeaderCell>
<VirtualTableHeaderCell <VirtualTableHeaderCell
className={styles.minimumAvailability} className={styles.minimumAvailability}
name="minimumAvailability" name="minimumAvailability"
> >
{translate('MinAvailability')} Min Availability
</VirtualTableHeaderCell> </VirtualTableHeaderCell>
<VirtualTableHeaderCell <VirtualTableHeaderCell
className={styles.qualityProfile} className={styles.qualityProfile}
name="qualityProfileId" name="qualityProfileId"
> >
{translate('QualityProfile')} Quality Profile
</VirtualTableHeaderCell> </VirtualTableHeaderCell>
<VirtualTableHeaderCell <VirtualTableHeaderCell
className={styles.movie} className={styles.movie}
name="movie" name="movie"
> >
{translate('Movie')} Movie
</VirtualTableHeaderCell> </VirtualTableHeaderCell>
</VirtualTableHeader> </VirtualTableHeader>
); );
@@ -9,7 +9,6 @@ import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Portal from 'Components/Portal'; import Portal from 'Components/Portal';
import { icons, kinds } from 'Helpers/Props'; import { icons, kinds } from 'Helpers/Props';
import getUniqueElememtId from 'Utilities/getUniqueElementId'; import getUniqueElememtId from 'Utilities/getUniqueElementId';
import translate from 'Utilities/String/translate';
import ImportMovieSearchResultConnector from './ImportMovieSearchResultConnector'; import ImportMovieSearchResultConnector from './ImportMovieSearchResultConnector';
import ImportMovieTitle from './ImportMovieTitle'; import ImportMovieTitle from './ImportMovieTitle';
import styles from './ImportMovieSelectMovie.css'; import styles from './ImportMovieSelectMovie.css';
@@ -175,7 +174,7 @@ class ImportMovieSelectMovie extends Component {
kind={kinds.WARNING} kind={kinds.WARNING}
/> />
{translate('NoMatchFound')} No match found!
</div> : </div> :
null null
} }
@@ -190,7 +189,7 @@ class ImportMovieSelectMovie extends Component {
kind={kinds.WARNING} kind={kinds.WARNING}
/> />
{translate('SearchFailedPleaseTryAgainLater')} Search failed, please try again later.
</div> : </div> :
null null
} }
@@ -2,7 +2,6 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import Label from 'Components/Label'; import Label from 'Components/Label';
import { kinds } from 'Helpers/Props'; import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './ImportMovieTitle.css'; import styles from './ImportMovieTitle.css';
function ImportMovieTitle(props) { function ImportMovieTitle(props) {
@@ -34,7 +33,7 @@ function ImportMovieTitle(props) {
<Label <Label
kind={kinds.WARNING} kind={kinds.WARNING}
> >
{translate('Existing')} Existing
</Label> </Label>
} }
</div> </div>
@@ -6,7 +6,6 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableRow from 'Components/Table/TableRow'; import TableRow from 'Components/Table/TableRow';
import { icons } from 'Helpers/Props'; import { icons } from 'Helpers/Props';
import formatBytes from 'Utilities/Number/formatBytes'; import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
import styles from './ImportMovieRootFolderRow.css'; import styles from './ImportMovieRootFolderRow.css';
function ImportMovieRootFolderRow(props) { function ImportMovieRootFolderRow(props) {
@@ -41,7 +40,7 @@ function ImportMovieRootFolderRow(props) {
<TableRowCell className={styles.actions}> <TableRowCell className={styles.actions}>
<IconButton <IconButton
title={translate('RemoveRootFolder')} title="Remove root folder"
name={icons.REMOVE} name={icons.REMOVE}
onPress={onDeletePress} onPress={onDeletePress}
/> />
@@ -17,17 +17,17 @@ import styles from './ImportMovieSelectFolder.css';
const rootFolderColumns = [ const rootFolderColumns = [
{ {
name: 'path', name: 'path',
label: translate('Path'), label: 'Path',
isVisible: true isVisible: true
}, },
{ {
name: 'freeSpace', name: 'freeSpace',
label: translate('FreeSpace'), label: 'Free Space',
isVisible: true isVisible: true
}, },
{ {
name: 'unmappedFolders', name: 'unmappedFolders',
label: translate('UnmappedFolders'), label: 'Unmapped Folders',
isVisible: true isVisible: true
}, },
{ {
@@ -77,7 +77,7 @@ class ImportMovieSelectFolder extends Component {
} = this.props; } = this.props;
return ( return (
<PageContent title={translate('ImportMovies')}> <PageContent title="Import Movies">
<PageContentBody> <PageContentBody>
{ {
isFetching && !isPopulated && isFetching && !isPopulated &&
@@ -86,9 +86,7 @@ class ImportMovieSelectFolder extends Component {
{ {
!isFetching && !!error && !isFetching && !!error &&
<div> <div>Unable to load root folders</div>
{translate('UnableToLoadRootFolders')}
</div>
} }
{ {
@@ -101,15 +99,19 @@ class ImportMovieSelectFolder extends Component {
<div className={styles.tips}> <div className={styles.tips}>
{translate('ImportTipsMessage')} {translate('ImportTipsMessage')}
<ul> <ul>
<li className={styles.tip} dangerouslySetInnerHTML={{ __html: translate('ImportIncludeQuality', ['<code>movie.2008.bluray.mkv</code>']) }} /> <li className={styles.tip}>
<li className={styles.tip} dangerouslySetInnerHTML={{ __html: translate('ImportRootPath', [`<code>${isWindows ? 'C:\\movies' : '/movies'}</code>`, `<code>${isWindows ? 'C:\\movies\\the matrix' : '/movies/the matrix'}</code>`]) }} /> Make sure that your files include the quality in their filenames. eg. <span className={styles.code}>movie.2008.bluray.mkv</span>
</li>
<li className={styles.tip}>
Point Radarr to the folder containing all of your movies, not a specific one. eg. <span className={styles.code}>"{isWindows ? 'C:\\movies' : '/movies'}"</span> and not <span className={styles.code}>"{isWindows ? 'C:\\movies\\the matrix' : '/movies/the matrix'}"</span>
</li>
</ul> </ul>
</div> </div>
{ {
items.length > 0 ? items.length > 0 ?
<div className={styles.recentFolders}> <div className={styles.recentFolders}>
<FieldSet legend={translate('RecentFolders')}> <FieldSet legend="Recent Folders">
<Table <Table
columns={rootFolderColumns} columns={rootFolderColumns}
> >
@@ -154,7 +156,7 @@ class ImportMovieSelectFolder extends Component {
className={styles.importButtonIcon} className={styles.importButtonIcon}
name={icons.DRIVE} name={icons.DRIVE}
/> />
{translate('StartImport')} Start Import
</Button> </Button>
</div> </div>
} }
+3 -3
View File
@@ -15,10 +15,10 @@ import MovieIndexConnector from 'Movie/Index/MovieIndexConnector';
import CustomFormatSettingsConnector from 'Settings/CustomFormats/CustomFormatSettingsConnector'; import CustomFormatSettingsConnector from 'Settings/CustomFormats/CustomFormatSettingsConnector';
import DownloadClientSettingsConnector from 'Settings/DownloadClients/DownloadClientSettingsConnector'; import DownloadClientSettingsConnector from 'Settings/DownloadClients/DownloadClientSettingsConnector';
import GeneralSettingsConnector from 'Settings/General/GeneralSettingsConnector'; import GeneralSettingsConnector from 'Settings/General/GeneralSettingsConnector';
import ImportListSettingsConnector from 'Settings/ImportLists/ImportListSettingsConnector';
import IndexerSettingsConnector from 'Settings/Indexers/IndexerSettingsConnector'; import IndexerSettingsConnector from 'Settings/Indexers/IndexerSettingsConnector';
import MediaManagementConnector from 'Settings/MediaManagement/MediaManagementConnector'; import MediaManagementConnector from 'Settings/MediaManagement/MediaManagementConnector';
import MetadataSettings from 'Settings/Metadata/MetadataSettings'; import MetadataSettings from 'Settings/Metadata/MetadataSettings';
import NetImportSettingsConnector from 'Settings/NetImport/NetImportSettingsConnector';
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 Quality from 'Settings/Quality/Quality'; import Quality from 'Settings/Quality/Quality';
@@ -156,8 +156,8 @@ function AppRoutes(props) {
/> />
<Route <Route
path="/settings/importlists" path="/settings/netimports"
component={ImportListSettingsConnector} component={NetImportSettingsConnector}
/> />
<Route <Route
+2 -3
View File
@@ -8,7 +8,6 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader'; import ModalHeader from 'Components/Modal/ModalHeader';
import { kinds } from 'Helpers/Props'; import { kinds } from 'Helpers/Props';
import UpdateChanges from 'System/Updates/UpdateChanges'; import UpdateChanges from 'System/Updates/UpdateChanges';
import translate from 'Utilities/String/translate';
import styles from './AppUpdatedModalContent.css'; import styles from './AppUpdatedModalContent.css';
function AppUpdatedModalContent(props) { function AppUpdatedModalContent(props) {
@@ -50,12 +49,12 @@ function AppUpdatedModalContent(props) {
</div> </div>
<UpdateChanges <UpdateChanges
title={translate('New')} title="New"
changes={update.changes.new} changes={update.changes.new}
/> />
<UpdateChanges <UpdateChanges
title={translate('Fixed')} title="Fixed"
changes={update.changes.fixed} changes={update.changes.fixed}
/> />
</div> </div>
+4 -5
View File
@@ -7,7 +7,6 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter'; import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader'; import ModalHeader from 'Components/Modal/ModalHeader';
import { kinds } from 'Helpers/Props'; import { kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './ConnectionLostModal.css'; import styles from './ConnectionLostModal.css';
function ConnectionLostModal(props) { function ConnectionLostModal(props) {
@@ -23,16 +22,16 @@ function ConnectionLostModal(props) {
> >
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
<ModalHeader> <ModalHeader>
{translate('ConnectionLost')} Connnection Lost
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
<div> <div>
{translate('ConnectionLostMessage')} Radarr has lost it's connection to the backend and will need to be reloaded to restore functionality.
</div> </div>
<div className={styles.automatic}> <div className={styles.automatic}>
{translate('ConnectionLostAutomaticMessage')} Radarr will try to connect automatically, or you can click reload below.
</div> </div>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
@@ -40,7 +39,7 @@ function ConnectionLostModal(props) {
kind={kinds.PRIMARY} kind={kinds.PRIMARY}
onPress={onModalClose} onPress={onModalClose}
> >
{translate('Reload')} Reload
</Button> </Button>
</ModalFooter> </ModalFooter>
</ModalContent> </ModalContent>
+2 -33
View File
@@ -6,38 +6,9 @@ import styles from './Agenda.css';
function Agenda(props) { function Agenda(props) {
const { const {
items, items
start,
end
} = props; } = props;
const startDateParsed = Date.parse(start);
const endDateParsed = Date.parse(end);
items.forEach((item) => {
const cinemaDateParsed = Date.parse(item.inCinemas);
const digitalDateParsed = Date.parse(item.digitalRelease);
const physicalDateParsed = Date.parse(item.physicalRelease);
const dates = [];
if (cinemaDateParsed > 0 && cinemaDateParsed >= startDateParsed && cinemaDateParsed <= endDateParsed) {
dates.push(cinemaDateParsed);
}
if (digitalDateParsed > 0 && digitalDateParsed >= startDateParsed && digitalDateParsed <= endDateParsed) {
dates.push(digitalDateParsed);
}
if (physicalDateParsed > 0 && physicalDateParsed >= startDateParsed && physicalDateParsed <= endDateParsed) {
dates.push(physicalDateParsed);
}
item.sortDate = Math.min(...dates);
item.cinemaDateParsed = cinemaDateParsed;
item.digitalDateParsed = digitalDateParsed;
item.physicalDateParsed = physicalDateParsed;
});
items.sort((a, b) => ((a.sortDate > b.sortDate) ? 1 : -1));
return ( return (
<div className={styles.agenda}> <div className={styles.agenda}>
{ {
@@ -61,9 +32,7 @@ function Agenda(props) {
} }
Agenda.propTypes = { Agenda.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired
start: PropTypes.string.isRequired,
end: PropTypes.string.isRequired
}; };
export default Agenda; export default Agenda;
+1 -14
View File
@@ -10,10 +10,6 @@
} }
} }
.link {
composes: link from '~Calendar/Events/CalendarEvent.css';
}
.eventWrapper { .eventWrapper {
display: flex; display: flex;
flex: 1 0 1px; flex: 1 0 1px;
@@ -34,8 +30,7 @@
border: none !important; border: none !important;
} }
.movieTitle, .movieTitle {
.genres {
@add-mixin truncate; @add-mixin truncate;
flex: 0 1 300px; flex: 0 1 300px;
@@ -66,10 +61,6 @@
composes: missing from '~Calendar/Events/CalendarEvent.css'; composes: missing from '~Calendar/Events/CalendarEvent.css';
} }
.unreleased {
composes: unreleased from '~Calendar/Events/CalendarEvent.css';
}
@media only screen and (max-width: $breakpointSmall) { @media only screen and (max-width: $breakpointSmall) {
.event { .event {
flex-direction: column; flex-direction: column;
@@ -90,7 +81,3 @@
flex: 0 0 100%; flex: 0 0 100%;
} }
} }
.dateIcon {
width: 25px;
}
+17 -67
View File
@@ -7,7 +7,7 @@ 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 translate from 'Utilities/String/translate'; import MovieTitleLink from 'Movie/MovieTitleLink';
import styles from './AgendaEvent.css'; import styles from './AgendaEvent.css';
class AgendaEvent extends Component { class AgendaEvent extends Component {
@@ -41,69 +41,35 @@ class AgendaEvent extends Component {
movieFile, movieFile,
title, title,
titleSlug, titleSlug,
genres,
isAvailable, isAvailable,
inCinemas, inCinemas,
digitalRelease,
physicalRelease,
monitored, monitored,
hasFile, hasFile,
grabbed, grabbed,
queueItem, queueItem,
showDate, showDate,
showMovieInformation,
showCutoffUnmetIcon, showCutoffUnmetIcon,
longDateFormat, longDateFormat,
colorImpairedMode, colorImpairedMode
cinemaDateParsed,
digitalDateParsed,
physicalDateParsed,
sortDate
} = this.props; } = this.props;
let startTime = null; const startTime = moment(inCinemas);
let releaseIcon = null;
if (physicalDateParsed === sortDate) {
startTime = physicalRelease;
releaseIcon = icons.DISC;
}
if (digitalDateParsed === sortDate) {
startTime = digitalRelease;
releaseIcon = icons.MOVIE_FILE;
}
if (cinemaDateParsed === sortDate) {
startTime = inCinemas;
releaseIcon = icons.IN_CINEMAS;
}
startTime = moment(startTime);
const downloading = !!(queueItem || grabbed); const downloading = !!(queueItem || grabbed);
const isMonitored = monitored; const isMonitored = monitored;
const statusStyle = getStatusStyle(hasFile, downloading, isAvailable, isMonitored); const statusStyle = getStatusStyle(hasFile, downloading, isAvailable, isMonitored);
const joinedGenres = genres.slice(0, 2).join(', ');
const link = `/movie/${titleSlug}`;
return ( return (
<div> <div>
<Link <Link
className={classNames( className={styles.event}
styles.event, component="div"
styles.link onPress={this.onPress}
)}
to={link}
> >
<div className={styles.dateIcon}>
<Icon
name={releaseIcon}
kind={kinds.DEFAULT}
/>
</div>
<div className={styles.date}> <div className={styles.date}>
{(showDate) ? startTime.format(longDateFormat) : null} {
showDate &&
startTime.format(longDateFormat)
}
</div> </div>
<div <div
@@ -114,16 +80,12 @@ class AgendaEvent extends Component {
)} )}
> >
<div className={styles.movieTitle}> <div className={styles.movieTitle}>
{title} <MovieTitleLink
titleSlug={titleSlug}
title={title}
/>
</div> </div>
{
showMovieInformation &&
<div className={styles.genres}>
{joinedGenres}
</div>
}
{ {
!!queueItem && !!queueItem &&
<span className={styles.statusIcon}> <span className={styles.statusIcon}>
@@ -138,7 +100,7 @@ class AgendaEvent extends Component {
<Icon <Icon
className={styles.statusIcon} className={styles.statusIcon}
name={icons.DOWNLOADING} name={icons.DOWNLOADING}
title={translate('MovieIsDownloading')} title="Movie is downloading"
/> />
} }
@@ -150,7 +112,7 @@ class AgendaEvent extends Component {
className={styles.statusIcon} className={styles.statusIcon}
name={icons.MOVIE_FILE} name={icons.MOVIE_FILE}
kind={kinds.WARNING} kind={kinds.WARNING}
title={translate('QualityCutoffHasNotBeenMet')} title="Quality cutoff has not been met"
/> />
} }
</div> </div>
@@ -165,29 +127,17 @@ AgendaEvent.propTypes = {
movieFile: PropTypes.object, movieFile: PropTypes.object,
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
titleSlug: PropTypes.string.isRequired, titleSlug: PropTypes.string.isRequired,
genres: PropTypes.arrayOf(PropTypes.string).isRequired,
isAvailable: PropTypes.bool.isRequired, isAvailable: PropTypes.bool.isRequired,
inCinemas: PropTypes.string, inCinemas: PropTypes.string,
digitalRelease: PropTypes.string,
physicalRelease: PropTypes.string,
monitored: PropTypes.bool.isRequired, monitored: PropTypes.bool.isRequired,
hasFile: PropTypes.bool.isRequired, hasFile: PropTypes.bool.isRequired,
grabbed: PropTypes.bool, grabbed: PropTypes.bool,
queueItem: PropTypes.object, queueItem: PropTypes.object,
showDate: PropTypes.bool.isRequired, showDate: PropTypes.bool.isRequired,
showMovieInformation: PropTypes.bool.isRequired,
showCutoffUnmetIcon: PropTypes.bool.isRequired, showCutoffUnmetIcon: PropTypes.bool.isRequired,
timeFormat: PropTypes.string.isRequired, timeFormat: PropTypes.string.isRequired,
longDateFormat: PropTypes.string.isRequired, longDateFormat: PropTypes.string.isRequired,
colorImpairedMode: PropTypes.bool.isRequired, colorImpairedMode: PropTypes.bool.isRequired
cinemaDateParsed: PropTypes.number,
digitalDateParsed: PropTypes.number,
physicalDateParsed: PropTypes.number,
sortDate: PropTypes.number
};
AgendaEvent.defaultProps = {
genres: []
}; };
export default AgendaEvent; export default AgendaEvent;
+1 -4
View File
@@ -1,7 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import translate from 'Utilities/String/translate';
import AgendaConnector from './Agenda/AgendaConnector'; import AgendaConnector from './Agenda/AgendaConnector';
import * as calendarViews from './calendarViews'; import * as calendarViews from './calendarViews';
import CalendarDaysConnector from './Day/CalendarDaysConnector'; import CalendarDaysConnector from './Day/CalendarDaysConnector';
@@ -31,9 +30,7 @@ class Calendar extends Component {
{ {
!isFetching && !!error && !isFetching && !!error &&
<div> <div>Unable to load the calendar</div>
{translate('UnableToLoadTheCalendar')}
</div>
} }
{ {
+5 -4
View File
@@ -76,15 +76,16 @@ class CalendarConnector extends Component {
} = this.props; } = this.props;
if (hasDifferentItems(prevProps.items, items)) { if (hasDifferentItems(prevProps.items, items)) {
const movieIds = selectUniqueIds(items, 'id');
const movieFileIds = selectUniqueIds(items, 'movieFileId'); const movieFileIds = selectUniqueIds(items, 'movieFileId');
if (items.length) {
this.props.fetchQueueDetails({ movieIds });
}
if (movieFileIds.length) { if (movieFileIds.length) {
this.props.fetchMovieFiles({ movieFileIds }); this.props.fetchMovieFiles({ movieFileIds });
} }
if (items.length) {
this.props.fetchQueueDetails();
}
} }
if (prevProps.time !== time) { if (prevProps.time !== time) {
+2 -2
View File
@@ -98,7 +98,7 @@ class CalendarPage extends Component {
const isMeasured = this.state.width > 0; const isMeasured = this.state.width > 0;
return ( return (
<PageContent title={translate('Calendar')}> <PageContent title="Calendar">
<PageToolbar> <PageToolbar>
<PageToolbarSection> <PageToolbarSection>
<PageToolbarButton <PageToolbarButton
@@ -110,7 +110,7 @@ class CalendarPage extends Component {
<PageToolbarSeparator /> <PageToolbarSeparator />
<PageToolbarButton <PageToolbarButton
label={translate('RSSSync')} label={translate('RssSync')}
iconName={icons.RSS} iconName={icons.RSS}
isSpinning={isRssSyncExecuting} isSpinning={isRssSyncExecuting}
onPress={onRssSyncPress} onPress={onRssSyncPress}
@@ -6,7 +6,6 @@ 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 translate from 'Utilities/String/translate';
import CalendarEventQueueDetails from './CalendarEventQueueDetails'; import CalendarEventQueueDetails from './CalendarEventQueueDetails';
import styles from './CalendarEvent.css'; import styles from './CalendarEvent.css';
@@ -86,7 +85,7 @@ class CalendarEvent extends Component {
<Icon <Icon
className={styles.statusIcon} className={styles.statusIcon}
name={icons.DOWNLOADING} name={icons.DOWNLOADING}
title={translate('MovieIsDownloading')} title="movie is downloading"
/> />
} }
@@ -98,7 +97,7 @@ class CalendarEvent extends Component {
className={styles.statusIcon} className={styles.statusIcon}
name={icons.MOVIE_FILE} name={icons.MOVIE_FILE}
kind={kinds.WARNING} kind={kinds.WARNING}
title={translate('QualityCutoffHasNotBeenMet')} title="Quality cutoff has not been met"
/> />
} }
</div> </div>
@@ -3,7 +3,6 @@ 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 colors from 'Styles/Variables/colors';
import translate from 'Utilities/String/translate';
function CalendarEventQueueDetails(props) { function CalendarEventQueueDetails(props) {
const { const {
@@ -12,12 +11,10 @@ function CalendarEventQueueDetails(props) {
sizeleft, sizeleft,
estimatedCompletionTime, estimatedCompletionTime,
status, status,
trackedDownloadState,
trackedDownloadStatus,
errorMessage errorMessage
} = props; } = props;
const progress = size ? (100 - sizeleft / size * 100) : 0; const progress = (100 - sizeleft / size * 100);
return ( return (
<QueueDetails <QueueDetails
@@ -26,11 +23,9 @@ function CalendarEventQueueDetails(props) {
sizeleft={sizeleft} sizeleft={sizeleft}
estimatedCompletionTime={estimatedCompletionTime} estimatedCompletionTime={estimatedCompletionTime}
status={status} status={status}
trackedDownloadState={trackedDownloadState}
trackedDownloadStatus={trackedDownloadStatus}
errorMessage={errorMessage} errorMessage={errorMessage}
progressBar={ progressBar={
<div title={translate('MovieIsDownloadingInterp', [progress.toFixed(1), title])}> <div title={`Movie is downloading - ${progress.toFixed(1)}% ${title}`}>
<CircularProgressBar <CircularProgressBar
progress={progress} progress={progress}
size={20} size={20}
@@ -49,8 +44,6 @@ CalendarEventQueueDetails.propTypes = {
sizeleft: PropTypes.number.isRequired, sizeleft: PropTypes.number.isRequired,
estimatedCompletionTime: PropTypes.string, estimatedCompletionTime: PropTypes.string,
status: PropTypes.string.isRequired, status: PropTypes.string.isRequired,
trackedDownloadState: PropTypes.string.isRequired,
trackedDownloadStatus: PropTypes.string.isRequired,
errorMessage: PropTypes.string errorMessage: PropTypes.string
}; };
@@ -24,7 +24,7 @@ function getTitle(time, start, end, view, longDateFormat) {
} else if (view === 'month') { } else if (view === 'month') {
return timeMoment.format('MMMM YYYY'); return timeMoment.format('MMMM YYYY');
} else if (view === 'agenda') { } else if (view === 'agenda') {
return `Agenda: ${startMoment.format('MMM D')} - ${endMoment.format('MMM D')}`; return 'Agenda';
} }
let startFormat = 'MMM D YYYY'; let startFormat = 'MMM D YYYY';
@@ -1,3 +1,4 @@
import _ from 'lodash';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@@ -13,16 +14,19 @@ function createMapStateToProps() {
createDimensionsSelector(), createDimensionsSelector(),
createUISettingsSelector(), createUISettingsSelector(),
(calendar, dimensions, uiSettings) => { (calendar, dimensions, uiSettings) => {
return { const result = _.pick(calendar, [
isFetching: calendar.isFetching, 'isFetching',
view: calendar.view, 'view',
time: calendar.time, 'time',
start: calendar.start, 'start',
end: calendar.end, 'end'
isSmallScreen: dimensions.isSmallScreen, ]);
collapseViewButtons: dimensions.isLargeScreen,
longDateFormat: uiSettings.longDateFormat result.isSmallScreen = dimensions.isSmallScreen;
}; result.collapseViewButtons = dimensions.isLargeScreen;
result.longDateFormat = uiSettings.longDateFormat;
return result;
} }
); );
} }
@@ -12,7 +12,6 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader'; import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes } from 'Helpers/Props'; import { inputTypes } from 'Helpers/Props';
import { firstDayOfWeekOptions, timeFormatOptions, weekColumnOptions } from 'Settings/UI/UISettings'; import { firstDayOfWeekOptions, timeFormatOptions, weekColumnOptions } from 'Settings/UI/UISettings';
import translate from 'Utilities/String/translate';
class CalendarOptionsModalContent extends Component { class CalendarOptionsModalContent extends Component {
@@ -111,38 +110,38 @@ class CalendarOptionsModalContent extends Component {
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
<FieldSet legend={translate('Local')}> <FieldSet legend="Local">
<Form> <Form>
<FormGroup> <FormGroup>
<FormLabel>{translate('ShowMovieInformation')}</FormLabel> <FormLabel>Show Movie Information</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="showMovieInformation" name="showMovieInformation"
value={showMovieInformation} value={showMovieInformation}
helpText={translate('ShowMovieInformationHelpText')} helpText="Show movie genres and certification"
onChange={this.onOptionInputChange} onChange={this.onOptionInputChange}
/> />
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<FormLabel>{translate('IconForCutoffUnmet')}</FormLabel> <FormLabel>Icon for Cutoff Unmet</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="showCutoffUnmetIcon" name="showCutoffUnmetIcon"
value={showCutoffUnmetIcon} value={showCutoffUnmetIcon}
helpText={translate('ShowCutoffUnmetIconHelpText')} helpText="Show icon for files when the cutoff hasn't been met"
onChange={this.onOptionInputChange} onChange={this.onOptionInputChange}
/> />
</FormGroup> </FormGroup>
</Form> </Form>
</FieldSet> </FieldSet>
<FieldSet legend={translate('Global')}> <FieldSet legend="Global">
<Form> <Form>
<FormGroup> <FormGroup>
<FormLabel>{translate('FirstDayOfWeek')}</FormLabel> <FormLabel>First Day of Week</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.SELECT} type={inputTypes.SELECT}
@@ -154,7 +153,7 @@ class CalendarOptionsModalContent extends Component {
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<FormLabel>{translate('WeekColumnHeader')}</FormLabel> <FormLabel>Week Column Header</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.SELECT} type={inputTypes.SELECT}
@@ -162,12 +161,12 @@ class CalendarOptionsModalContent extends Component {
values={weekColumnOptions} values={weekColumnOptions}
value={calendarWeekColumnHeader} value={calendarWeekColumnHeader}
onChange={this.onGlobalInputChange} onChange={this.onGlobalInputChange}
helpText={translate('HelpText')} helpText="Shown above each column when week is the active view"
/> />
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<FormLabel>{translate('TimeFormat')}</FormLabel> <FormLabel>Time Format</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.SELECT} type={inputTypes.SELECT}
@@ -177,13 +176,13 @@ class CalendarOptionsModalContent extends Component {
onChange={this.onGlobalInputChange} onChange={this.onGlobalInputChange}
/> />
</FormGroup><FormGroup> </FormGroup><FormGroup>
<FormLabel>{translate('EnableColorImpairedMode')}</FormLabel> <FormLabel>Enable Color-Impaired Mode</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="enableColorImpairedMode" name="enableColorImpairedMode"
value={enableColorImpairedMode} value={enableColorImpairedMode}
helpText={translate('EnableColorImpairedModeHelpText')} helpText="Altered style to allow color-impaired users to better distinguish color coded information"
onChange={this.onGlobalInputChange} onChange={this.onGlobalInputChange}
/> />
</FormGroup> </FormGroup>
@@ -194,7 +193,7 @@ class CalendarOptionsModalContent extends Component {
<ModalFooter> <ModalFooter>
<Button onPress={onModalClose}> <Button onPress={onModalClose}>
{translate('Close')} Close
</Button> </Button>
</ModalFooter> </ModalFooter>
</ModalContent> </ModalContent>
@@ -13,7 +13,6 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter'; import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader'; import ModalHeader from 'Components/Modal/ModalHeader';
import { icons, inputTypes, kinds, sizes } from 'Helpers/Props'; import { icons, inputTypes, kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
function getUrls(state) { function getUrls(state) {
const { const {
@@ -115,37 +114,37 @@ class CalendarLinkModalContent extends Component {
<ModalBody> <ModalBody>
<Form> <Form>
<FormGroup> <FormGroup>
<FormLabel>{translate('IncludeUnmonitored')}</FormLabel> <FormLabel>Include Unmonitored</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="unmonitored" name="unmonitored"
value={unmonitored} value={unmonitored}
helpText={translate('UnmonitoredHelpText')} helpText="Include unmonitored movies in the iCal feed"
onChange={this.onInputChange} onChange={this.onInputChange}
/> />
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<FormLabel>{translate('ShowAsAllDayEvents')}</FormLabel> <FormLabel>Show as All-Day Events</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="asAllDay" name="asAllDay"
value={asAllDay} value={asAllDay}
helpText={translate('AsAllDayHelpText')} helpText="Events will appear as all-day events in your calendar"
onChange={this.onInputChange} onChange={this.onInputChange}
/> />
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<FormLabel>{translate('Tags')}</FormLabel> <FormLabel>Tags</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.TAG} type={inputTypes.TAG}
name="tags" name="tags"
value={tags} value={tags}
helpText={translate('TagsHelpText')} helpText="Feed will only contain movies with at least one matching tag"
onChange={this.onInputChange} onChange={this.onInputChange}
/> />
</FormGroup> </FormGroup>
@@ -153,14 +152,14 @@ class CalendarLinkModalContent extends Component {
<FormGroup <FormGroup
size={sizes.LARGE} size={sizes.LARGE}
> >
<FormLabel>{translate('ICalFeed')}</FormLabel> <FormLabel>iCal Feed</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.TEXT} type={inputTypes.TEXT}
name="iCalHttpUrl" name="iCalHttpUrl"
value={iCalHttpUrl} value={iCalHttpUrl}
readOnly={true} readOnly={true}
helpText={translate('ICalHttpUrlHelpText')} helpText="Copy this URL to your client(s) or click to subscribe if your browser supports webcal"
buttons={[ buttons={[
<ClipboardButton <ClipboardButton
key="copy" key="copy"
@@ -187,7 +186,7 @@ class CalendarLinkModalContent extends Component {
<ModalFooter> <ModalFooter>
<Button onPress={onModalClose}> <Button onPress={onModalClose}>
{translate('Close')} Close
</Button> </Button>
</ModalFooter> </ModalFooter>
</ModalContent> </ModalContent>
-1
View File
@@ -16,4 +16,3 @@ export const RENAME_MOVIE = 'RenameMovie';
export const RESET_API_KEY = 'ResetApiKey'; export const RESET_API_KEY = 'ResetApiKey';
export const RSS_SYNC = 'RssSync'; export const RSS_SYNC = 'RssSync';
export const MOVIE_SEARCH = 'MoviesSearch'; export const MOVIE_SEARCH = 'MoviesSearch';
export const IMPORT_LIST_SYNC = 'ImportListSync';
@@ -14,19 +14,18 @@ import Scroller from 'Components/Scroller/Scroller';
import Table from 'Components/Table/Table'; import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody'; import TableBody from 'Components/Table/TableBody';
import { kinds, scrollDirections } from 'Helpers/Props'; import { kinds, scrollDirections } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import FileBrowserRow from './FileBrowserRow'; import FileBrowserRow from './FileBrowserRow';
import styles from './FileBrowserModalContent.css'; import styles from './FileBrowserModalContent.css';
const columns = [ const columns = [
{ {
name: 'type', name: 'type',
label: translate('Type'), label: 'Type',
isVisible: true isVisible: true
}, },
{ {
name: 'name', name: 'name',
label: translate('Name'), label: 'Name',
isVisible: true isVisible: true
} }
]; ];
@@ -135,7 +134,7 @@ class FileBrowserModalContent extends Component {
<PathInput <PathInput
className={styles.pathInput} className={styles.pathInput}
placeholder={translate('StartTypingOrSelectAPathBelow')} placeholder="Start typing or select a path below"
hasFileBrowser={false} hasFileBrowser={false}
{...otherProps} {...otherProps}
value={this.state.currentPath} value={this.state.currentPath}
@@ -149,9 +148,7 @@ class FileBrowserModalContent extends Component {
> >
{ {
!!error && !!error &&
<div> <div>Error loading contents</div>
{translate('ErrorLoadingContents')}
</div>
} }
{ {
@@ -226,13 +223,13 @@ class FileBrowserModalContent extends Component {
<Button <Button
onPress={onModalClose} onPress={onModalClose}
> >
{translate('Cancel')} Cancel
</Button> </Button>
<Button <Button
onPress={this.onOkPress} onPress={this.onOkPress}
> >
{translate('Ok')} Ok
</Button> </Button>
</ModalFooter> </ModalFooter>
</ModalContent> </ModalContent>
@@ -3,7 +3,7 @@ import React, { Component } from 'react';
import NumberInput from 'Components/Form/NumberInput'; import NumberInput from 'Components/Form/NumberInput';
import SelectInput from 'Components/Form/SelectInput'; import SelectInput from 'Components/Form/SelectInput';
import TextInput from 'Components/Form/TextInput'; import TextInput from 'Components/Form/TextInput';
import { IN_LAST, IN_NEXT, NOT_IN_LAST, NOT_IN_NEXT } from 'Helpers/Props/filterTypes'; import { IN_LAST, IN_NEXT } from 'Helpers/Props/filterTypes';
import isString from 'Utilities/String/isString'; import isString from 'Utilities/String/isString';
import { NAME } from './FilterBuilderRowValue'; import { NAME } from './FilterBuilderRowValue';
import styles from './DateFilterBuilderRowValue.css'; import styles from './DateFilterBuilderRowValue.css';
@@ -18,12 +18,7 @@ const timeOptions = [
]; ];
function isInFilter(filterType) { function isInFilter(filterType) {
return ( return filterType === IN_LAST || filterType === IN_NEXT;
filterType === IN_LAST ||
filterType === NOT_IN_LAST ||
filterType === IN_NEXT ||
filterType === NOT_IN_NEXT
);
} }
class DateFilterBuilderRowValue extends Component { class DateFilterBuilderRowValue extends Component {
@@ -8,7 +8,6 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter'; import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader'; import ModalHeader from 'Components/Modal/ModalHeader';
import { inputTypes } from 'Helpers/Props'; import { inputTypes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import FilterBuilderRow from './FilterBuilderRow'; import FilterBuilderRow from './FilterBuilderRow';
import styles from './FilterBuilderModalContent.css'; import styles from './FilterBuilderModalContent.css';
@@ -193,7 +192,7 @@ class FilterBuilderModalContent extends Component {
<ModalFooter> <ModalFooter>
<Button onPress={onCancelPress}> <Button onPress={onCancelPress}>
{translate('Cancel')} Cancel
</Button> </Button>
<SpinnerErrorButton <SpinnerErrorButton
@@ -201,7 +200,7 @@ class FilterBuilderModalContent extends Component {
error={saveError} error={saveError}
onPress={this.onSaveFilterPress} onPress={this.onSaveFilterPress}
> >
{translate('Save')} Save
</SpinnerErrorButton> </SpinnerErrorButton>
</ModalFooter> </ModalFooter>
</ModalContent> </ModalContent>
@@ -6,7 +6,6 @@ import { filterBuilderTypes, filterBuilderValueTypes, icons } from 'Helpers/Prop
import BoolFilterBuilderRowValue from './BoolFilterBuilderRowValue'; import BoolFilterBuilderRowValue from './BoolFilterBuilderRowValue';
import DateFilterBuilderRowValue from './DateFilterBuilderRowValue'; import DateFilterBuilderRowValue from './DateFilterBuilderRowValue';
import FilterBuilderRowValueConnector from './FilterBuilderRowValueConnector'; import FilterBuilderRowValueConnector from './FilterBuilderRowValueConnector';
import ImportListFilterBuilderRowValueConnector from './ImportListFilterBuilderRowValueConnector';
import IndexerFilterBuilderRowValueConnector from './IndexerFilterBuilderRowValueConnector'; import IndexerFilterBuilderRowValueConnector from './IndexerFilterBuilderRowValueConnector';
import MovieStatusFilterBuilderRowValue from './MovieStatusFilterBuilderRowValue'; import MovieStatusFilterBuilderRowValue from './MovieStatusFilterBuilderRowValue';
import ProtocolFilterBuilderRowValue from './ProtocolFilterBuilderRowValue'; import ProtocolFilterBuilderRowValue from './ProtocolFilterBuilderRowValue';
@@ -75,9 +74,6 @@ function getRowValueConnector(selectedFilterBuilderProp) {
case filterBuilderValueTypes.TAG: case filterBuilderValueTypes.TAG:
return TagFilterBuilderRowValueConnector; return TagFilterBuilderRowValueConnector;
case filterBuilderValueTypes.IMPORTLIST:
return ImportListFilterBuilderRowValueConnector;
default: default:
return FilterBuilderRowValueConnector; return FilterBuilderRowValueConnector;
} }
@@ -1,27 +0,0 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createImportListSelector from 'Store/Selectors/createImportListSelector';
import FilterBuilderRowValue from './FilterBuilderRowValue';
function createMapStateToProps() {
return createSelector(
createImportListSelector(),
(importLists) => {
return {
tagList: importLists.map((importList) => {
const {
id,
name
} = importList;
return {
id,
name
};
})
};
}
);
}
export default connect(createMapStateToProps)(FilterBuilderRowValue);
@@ -3,7 +3,6 @@ import React, { Component } from 'react';
import IconButton from 'Components/Link/IconButton'; import IconButton from 'Components/Link/IconButton';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton'; import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import { icons } from 'Helpers/Props'; import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './CustomFilter.css'; import styles from './CustomFilter.css';
class CustomFilter extends Component { class CustomFilter extends Component {
@@ -90,7 +89,7 @@ class CustomFilter extends Component {
/> />
<SpinnerIconButton <SpinnerIconButton
title={translate('RemoveFilter')} title="Remove filter"
name={icons.REMOVE} name={icons.REMOVE}
isSpinning={this.state.isDeleting} isSpinning={this.state.isDeleting}
onPress={this.onRemovePress} onPress={this.onRemovePress}
@@ -59,7 +59,7 @@ function CustomFiltersModalContent(props) {
<Button <Button
onPress={onModalClose} onPress={onModalClose}
> >
{translate('Close')} Close
</Button> </Button>
</ModalFooter> </ModalFooter>
</ModalContent> </ModalContent>
@@ -8,28 +8,16 @@ import DeviceInput from './DeviceInput';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
(state, { value }) => value, (state, { value }) => value,
(state, { name }) => name,
(state) => state.providerOptions, (state) => state.providerOptions,
(value, name, devices) => { (value, devices) => {
const {
isFetching,
isPopulated,
error,
items
} = devices;
return { return {
isFetching, ...devices,
isPopulated,
error,
items: items[name] || [],
selectedDevices: value.map((valueDevice) => { selectedDevices: value.map((valueDevice) => {
const sectionItems = items[name] || [];
// Disable equality ESLint rule so we don't need to worry about // Disable equality ESLint rule so we don't need to worry about
// a type mismatch between the value items and the device ID. // a type mismatch between the value items and the device ID.
// eslint-disable-next-line eqeqeq // eslint-disable-next-line eqeqeq
const device = sectionItems.find((d) => d.id == valueDevice); const device = devices.items.find((d) => d.id == valueDevice);
if (device) { if (device) {
return { return {
@@ -73,14 +61,11 @@ class DeviceInputConnector extends Component {
const { const {
provider, provider,
providerData, providerData,
dispatchFetchOptions, dispatchFetchOptions
requestAction,
name
} = this.props; } = this.props;
dispatchFetchOptions({ dispatchFetchOptions({
action: requestAction, action: 'getDevices',
itemSection: name,
provider, provider,
providerData providerData
}); });
@@ -109,7 +94,6 @@ class DeviceInputConnector extends Component {
DeviceInputConnector.propTypes = { DeviceInputConnector.propTypes = {
provider: PropTypes.string.isRequired, provider: PropTypes.string.isRequired,
providerData: PropTypes.object.isRequired, providerData: PropTypes.object.isRequired,
requestAction: PropTypes.string.isRequired,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
dispatchFetchOptions: PropTypes.func.isRequired, dispatchFetchOptions: PropTypes.func.isRequired,
@@ -58,30 +58,11 @@ function getSelectedIndex(props) {
values values
} = props; } = props;
if (Array.isArray(value)) {
return values.findIndex((v) => {
return value.size && v.key === value[0];
});
}
return values.findIndex((v) => { return values.findIndex((v) => {
return v.key === value; return v.key === value;
}); });
} }
function isSelectedItem(index, props) {
const {
value,
values
} = props;
if (Array.isArray(value)) {
return value.includes(values[index].key);
}
return values[index].key === value;
}
function getKey(selectedIndex, values) { function getKey(selectedIndex, values) {
return values[selectedIndex].key; return values[selectedIndex].key;
} }
@@ -111,7 +92,7 @@ class EnhancedSelectInput extends Component {
this._scheduleUpdate(); this._scheduleUpdate();
} }
if (!Array.isArray(this.props.value) && prevProps.value !== this.props.value) { if (prevProps.value !== this.props.value) {
this.setState({ this.setState({
selectedIndex: getSelectedIndex(this.props) selectedIndex: getSelectedIndex(this.props)
}); });
@@ -153,7 +134,7 @@ class EnhancedSelectInput extends Component {
const button = document.getElementById(this._buttonId); const button = document.getElementById(this._buttonId);
const options = document.getElementById(this._optionsId); const options = document.getElementById(this._optionsId);
if (!button || !event.target.isConnected || this.state.isMobile) { if (!button || this.state.isMobile) {
return; return;
} }
@@ -196,7 +177,7 @@ class EnhancedSelectInput extends Component {
} }
if ( if (
selectedIndex == null || selectedIndex === -1 || selectedIndex == null ||
getSelectedOption(selectedIndex, values).isDisabled getSelectedOption(selectedIndex, values).isDisabled
) { ) {
if (keyCode === keyCodes.UP_ARROW) { if (keyCode === keyCodes.UP_ARROW) {
@@ -254,27 +235,12 @@ class EnhancedSelectInput extends Component {
} }
onSelect = (value) => { onSelect = (value) => {
if (Array.isArray(this.props.value)) { this.setState({ isOpen: false });
let newValue = null;
const index = this.props.value.indexOf(value);
if (index === -1) {
newValue = this.props.values.map((v) => v.key).filter((v) => (v === value) || this.props.value.includes(v));
} else {
newValue = [...this.props.value];
newValue.splice(index, 1);
}
this.props.onChange({
name: this.props.name,
value: newValue
});
} else {
this.setState({ isOpen: false });
this.props.onChange({ this.props.onChange({
name: this.props.name, name: this.props.name,
value value
}); });
}
} }
onMeasure = ({ width }) => { onMeasure = ({ width }) => {
@@ -292,7 +258,6 @@ class EnhancedSelectInput extends Component {
const { const {
className, className,
disabledClassName, disabledClassName,
value,
values, values,
isDisabled, isDisabled,
hasError, hasError,
@@ -310,7 +275,6 @@ class EnhancedSelectInput extends Component {
isMobile isMobile
} = this.state; } = this.state;
const isMultiSelect = Array.isArray(value);
const selectedOption = getSelectedOption(selectedIndex, values); const selectedOption = getSelectedOption(selectedIndex, values);
return ( return (
@@ -339,12 +303,9 @@ class EnhancedSelectInput extends Component {
onPress={this.onPress} onPress={this.onPress}
> >
<SelectedValueComponent <SelectedValueComponent
value={value}
values={values}
{...selectedValueOptions} {...selectedValueOptions}
{...selectedOption} {...selectedOption}
isDisabled={isDisabled} isDisabled={isDisabled}
isMultiSelect={isMultiSelect}
> >
{selectedOption ? selectedOption.value : null} {selectedOption ? selectedOption.value : null}
</SelectedValueComponent> </SelectedValueComponent>
@@ -398,17 +359,11 @@ class EnhancedSelectInput extends Component {
> >
{ {
values.map((v, index) => { values.map((v, index) => {
const hasParent = v.parentKey !== undefined;
const depth = hasParent ? 1 : 0;
const parentSelected = hasParent && value.includes(v.parentKey);
return ( return (
<OptionComponent <OptionComponent
key={v.key} key={v.key}
id={v.key} id={v.key}
depth={depth} isSelected={index === selectedIndex}
isSelected={isSelectedItem(index, this.props)}
isDisabled={parentSelected}
isMultiSelect={isMultiSelect}
{...valueOptions} {...valueOptions}
{...v} {...v}
isMobile={false} isMobile={false}
@@ -446,17 +401,11 @@ class EnhancedSelectInput extends Component {
<Scroller className={styles.optionsModalScroller}> <Scroller className={styles.optionsModalScroller}>
{ {
values.map((v, index) => { values.map((v, index) => {
const hasParent = v.parentKey !== undefined;
const depth = hasParent ? 1 : 0;
const parentSelected = hasParent && value.includes(v.parentKey);
return ( return (
<OptionComponent <OptionComponent
key={v.key} key={v.key}
id={v.key} id={v.key}
depth={depth} isSelected={index === selectedIndex}
isSelected={isSelectedItem(index, this.props)}
isMultiSelect={isMultiSelect}
isDisabled={parentSelected}
{...valueOptions} {...valueOptions}
{...v} {...v}
isMobile={true} isMobile={true}
@@ -480,9 +429,9 @@ EnhancedSelectInput.propTypes = {
className: PropTypes.string, className: PropTypes.string,
disabledClassName: PropTypes.string, disabledClassName: PropTypes.string,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.arrayOf(PropTypes.number)]).isRequired, value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired, values: PropTypes.arrayOf(PropTypes.object).isRequired,
isDisabled: PropTypes.bool.isRequired, isDisabled: PropTypes.bool,
hasError: PropTypes.bool, hasError: PropTypes.bool,
hasWarning: PropTypes.bool, hasWarning: PropTypes.bool,
valueOptions: PropTypes.object.isRequired, valueOptions: PropTypes.object.isRequired,
@@ -11,18 +11,6 @@
} }
} }
.optionCheck {
composes: container from '~./CheckInput.css';
flex: 0 0 0;
}
.optionCheckInput {
composes: input from '~./CheckInput.css';
margin-top: 0;
}
.isSelected { .isSelected {
background-color: #e2e2e2; background-color: #e2e2e2;
@@ -4,7 +4,6 @@ import React, { Component } from 'react';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import Link from 'Components/Link/Link'; import Link from 'Components/Link/Link';
import { icons } from 'Helpers/Props'; import { icons } from 'Helpers/Props';
import CheckInput from './CheckInput';
import styles from './EnhancedSelectInputOption.css'; import styles from './EnhancedSelectInputOption.css';
class EnhancedSelectInputOption extends Component { class EnhancedSelectInputOption extends Component {
@@ -21,21 +20,15 @@ class EnhancedSelectInputOption extends Component {
onSelect(id); onSelect(id);
} }
onCheckPress = () => {
// CheckInput requires a handler. Swallow the change event because onPress will already handle it via event propagation.
}
// //
// Render // Render
render() { render() {
const { const {
className, className,
id,
isSelected, isSelected,
isDisabled, isDisabled,
isHidden, isHidden,
isMultiSelect,
isMobile, isMobile,
children children
} = this.props; } = this.props;
@@ -44,8 +37,8 @@ class EnhancedSelectInputOption extends Component {
<Link <Link
className={classNames( className={classNames(
className, className,
isSelected && !isMultiSelect && styles.isSelected, isSelected && styles.isSelected,
isDisabled && !isMultiSelect && styles.isDisabled, isDisabled && styles.isDisabled,
isHidden && styles.isHidden, isHidden && styles.isHidden,
isMobile && styles.isMobile isMobile && styles.isMobile
)} )}
@@ -53,19 +46,6 @@ class EnhancedSelectInputOption extends Component {
isDisabled={isDisabled} isDisabled={isDisabled}
onPress={this.onPress} onPress={this.onPress}
> >
{
isMultiSelect &&
<CheckInput
className={styles.optionCheckInput}
containerClassName={styles.optionCheck}
name={`select-${id}`}
value={isSelected}
isDisabled={isDisabled}
onChange={this.onCheckPress}
/>
}
{children} {children}
{ {
@@ -87,7 +67,6 @@ EnhancedSelectInputOption.propTypes = {
isSelected: PropTypes.bool.isRequired, isSelected: PropTypes.bool.isRequired,
isDisabled: PropTypes.bool.isRequired, isDisabled: PropTypes.bool.isRequired,
isHidden: PropTypes.bool.isRequired, isHidden: PropTypes.bool.isRequired,
isMultiSelect: PropTypes.bool.isRequired,
isMobile: PropTypes.bool.isRequired, isMobile: PropTypes.bool.isRequired,
children: PropTypes.node.isRequired, children: PropTypes.node.isRequired,
onSelect: PropTypes.func.isRequired onSelect: PropTypes.func.isRequired
@@ -96,8 +75,7 @@ EnhancedSelectInputOption.propTypes = {
EnhancedSelectInputOption.defaultProps = { EnhancedSelectInputOption.defaultProps = {
className: styles.option, className: styles.option,
isDisabled: false, isDisabled: false,
isHidden: false, isHidden: false
isMultiSelect: false
}; };
export default EnhancedSelectInputOption; export default EnhancedSelectInputOption;
+3 -11
View File
@@ -10,7 +10,6 @@ import CheckInput from './CheckInput';
import DeviceInputConnector from './DeviceInputConnector'; import DeviceInputConnector from './DeviceInputConnector';
import EnhancedSelectInput from './EnhancedSelectInput'; import EnhancedSelectInput from './EnhancedSelectInput';
import FormInputHelpText from './FormInputHelpText'; import FormInputHelpText from './FormInputHelpText';
import IndexerFlagsSelectInputConnector from './IndexerFlagsSelectInputConnector';
import KeyValueListInput from './KeyValueListInput'; import KeyValueListInput from './KeyValueListInput';
import MovieMonitoredSelectInput from './MovieMonitoredSelectInput'; import MovieMonitoredSelectInput from './MovieMonitoredSelectInput';
import NumberInput from './NumberInput'; import NumberInput from './NumberInput';
@@ -21,7 +20,6 @@ import QualityProfileSelectInputConnector from './QualityProfileSelectInputConne
import RootFolderSelectInputConnector from './RootFolderSelectInputConnector'; import RootFolderSelectInputConnector from './RootFolderSelectInputConnector';
import TagInputConnector from './TagInputConnector'; import TagInputConnector from './TagInputConnector';
import TagSelectInputConnector from './TagSelectInputConnector'; import TagSelectInputConnector from './TagSelectInputConnector';
import TextArea from './TextArea';
import TextInput from './TextInput'; import TextInput from './TextInput';
import TextTagInputConnector from './TextTagInputConnector'; import TextTagInputConnector from './TextTagInputConnector';
import styles from './FormInputGroup.css'; import styles from './FormInputGroup.css';
@@ -67,18 +65,12 @@ function getComponent(type) {
case inputTypes.ROOT_FOLDER_SELECT: case inputTypes.ROOT_FOLDER_SELECT:
return RootFolderSelectInputConnector; return RootFolderSelectInputConnector;
case inputTypes.INDEXER_FLAGS_SELECT:
return IndexerFlagsSelectInputConnector;
case inputTypes.SELECT: case inputTypes.SELECT:
return EnhancedSelectInput; return EnhancedSelectInput;
case inputTypes.TAG: case inputTypes.TAG:
return TagInputConnector; return TagInputConnector;
case inputTypes.TEXT_AREA:
return TextArea;
case inputTypes.TEXT_TAG: case inputTypes.TEXT_TAG:
return TextTagInputConnector; return TextTagInputConnector;
@@ -161,7 +153,7 @@ function FormInputGroup(props) {
<Icon <Icon
name={icons.UNSAVED_SETTING} name={icons.UNSAVED_SETTING}
className={styles.pendingChangesIcon} className={styles.pendingChangesIcon}
title={translate('ChangeHasNotBeenSavedYet')} title="Change has not been saved yet"
/> />
} }
</div> */} </div> */}
@@ -215,7 +207,7 @@ function FormInputGroup(props) {
key={index} key={index}
text={error.message} text={error.message}
link={error.link} link={error.link}
tooltip={error.detailedMessage} linkTooltip={error.detailedMessage}
isError={true} isError={true}
isCheckInput={checkInput} isCheckInput={checkInput}
/> />
@@ -230,7 +222,7 @@ function FormInputGroup(props) {
key={index} key={index}
text={warning.message} text={warning.message}
link={warning.link} link={warning.link}
tooltip={warning.detailedMessage} linkTooltip={warning.detailedMessage}
isWarning={true} isWarning={true}
isCheckInput={checkInput} isCheckInput={checkInput}
/> />
@@ -37,7 +37,3 @@
margin-left: 5px; margin-left: 5px;
} }
.details {
margin-left: 5px;
}
@@ -11,7 +11,7 @@ function FormInputHelpText(props) {
className, className,
text, text,
link, link,
tooltip, linkTooltip,
isError, isError,
isWarning, isWarning,
isCheckInput isCheckInput
@@ -28,27 +28,16 @@ function FormInputHelpText(props) {
{text} {text}
{ {
link ? !!link &&
<Link <Link
className={styles.link} className={styles.link}
to={link} to={link}
title={tooltip} title={linkTooltip}
> >
<Icon <Icon
name={icons.EXTERNAL_LINK} name={icons.EXTERNAL_LINK}
/> />
</Link> : </Link>
null
}
{
!link && tooltip ?
<Icon
containerClassName={styles.details}
name={icons.INFO}
title={tooltip}
/> :
null
} }
</div> </div>
); );
@@ -58,7 +47,7 @@ FormInputHelpText.propTypes = {
className: PropTypes.string.isRequired, className: PropTypes.string.isRequired,
text: PropTypes.string.isRequired, text: PropTypes.string.isRequired,
link: PropTypes.string, link: PropTypes.string,
tooltip: PropTypes.string, linkTooltip: PropTypes.string,
isError: PropTypes.bool, isError: PropTypes.bool,
isWarning: PropTypes.bool, isWarning: PropTypes.bool,
isCheckInput: PropTypes.bool isCheckInput: PropTypes.bool
@@ -6,23 +6,14 @@ import styles from './HintedSelectInputOption.css';
function HintedSelectInputOption(props) { function HintedSelectInputOption(props) {
const { const {
id,
value, value,
hint, hint,
isSelected,
isDisabled,
isMultiSelect,
isMobile, isMobile,
...otherProps ...otherProps
} = props; } = props;
return ( return (
<EnhancedSelectInputOption <EnhancedSelectInputOption
id={id}
isSelected={isSelected}
isDisabled={isDisabled}
isHidden={isDisabled}
isMultiSelect={isMultiSelect}
isMobile={isMobile} isMobile={isMobile}
{...otherProps} {...otherProps}
> >
@@ -45,19 +36,9 @@ function HintedSelectInputOption(props) {
} }
HintedSelectInputOption.propTypes = { HintedSelectInputOption.propTypes = {
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
value: PropTypes.string.isRequired, value: PropTypes.string.isRequired,
hint: PropTypes.node, hint: PropTypes.node,
isSelected: PropTypes.bool.isRequired,
isDisabled: PropTypes.bool.isRequired,
isMultiSelect: PropTypes.bool.isRequired,
isMobile: PropTypes.bool.isRequired isMobile: PropTypes.bool.isRequired
}; };
HintedSelectInputOption.defaultProps = {
isDisabled: false,
isHidden: false,
isMultiSelect: false
};
export default HintedSelectInputOption; export default HintedSelectInputOption;
@@ -1,43 +1,23 @@
import _ from 'lodash';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import Label from 'Components/Label';
import EnhancedSelectInputSelectedValue from './EnhancedSelectInputSelectedValue'; import EnhancedSelectInputSelectedValue from './EnhancedSelectInputSelectedValue';
import styles from './HintedSelectInputSelectedValue.css'; import styles from './HintedSelectInputSelectedValue.css';
function HintedSelectInputSelectedValue(props) { function HintedSelectInputSelectedValue(props) {
const { const {
value, value,
values,
hint, hint,
isMultiSelect,
includeHint, includeHint,
...otherProps ...otherProps
} = props; } = props;
const valuesMap = isMultiSelect && _.keyBy(values, 'key');
return ( return (
<EnhancedSelectInputSelectedValue <EnhancedSelectInputSelectedValue
className={styles.selectedValue} className={styles.selectedValue}
{...otherProps} {...otherProps}
> >
<div className={styles.valueText}> <div className={styles.valueText}>
{ {value}
isMultiSelect &&
value.map((key, index) => {
const v = valuesMap[key];
return (
<Label key={key}>
{v ? v.value : key}
</Label>
);
})
}
{
!isMultiSelect && value
}
</div> </div>
{ {
@@ -51,15 +31,12 @@ function HintedSelectInputSelectedValue(props) {
} }
HintedSelectInputSelectedValue.propTypes = { HintedSelectInputSelectedValue.propTypes = {
value: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number]))]).isRequired, value: PropTypes.string,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
hint: PropTypes.string, hint: PropTypes.string,
isMultiSelect: PropTypes.bool.isRequired,
includeHint: PropTypes.bool.isRequired includeHint: PropTypes.bool.isRequired
}; };
HintedSelectInputSelectedValue.defaultProps = { HintedSelectInputSelectedValue.defaultProps = {
isMultiSelect: false,
includeHint: true includeHint: true
}; };
@@ -1,70 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import EnhancedSelectInput from './EnhancedSelectInput';
function createMapStateToProps() {
return createSelector(
(state, { indexerFlags }) => indexerFlags,
(state) => state.settings.indexerFlags,
(selectedFlags, indexerFlags) => {
const value = [];
indexerFlags.items.forEach((item) => {
// eslint-disable-next-line no-bitwise
if ((selectedFlags & item.id) === item.id) {
value.push(item.id);
}
});
const values = indexerFlags.items.map(({ id, name }) => {
return {
key: id,
value: name
};
});
return {
value,
values
};
}
);
}
class IndexerFlagsSelectInputConnector extends Component {
onChange = ({ name, value }) => {
let indexerFlags = 0;
value.forEach((flagId) => {
indexerFlags += flagId;
});
this.props.onChange({ name, value: indexerFlags });
}
//
// Render
render() {
return (
<EnhancedSelectInput
{...this.props}
onChange={this.onChange}
/>
);
}
}
IndexerFlagsSelectInputConnector.propTypes = {
name: PropTypes.string.isRequired,
indexerFlags: PropTypes.number.isRequired,
value: PropTypes.arrayOf(PropTypes.number).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
onChange: PropTypes.func.isRequired
};
export default connect(createMapStateToProps)(IndexerFlagsSelectInputConnector);
@@ -3,17 +3,10 @@ import React from 'react';
import TextInput from './TextInput'; import TextInput from './TextInput';
import styles from './PasswordInput.css'; import styles from './PasswordInput.css';
// Prevent a user from copying (or cutting) the password from the input
function onCopy(e) {
e.preventDefault();
e.nativeEvent.stopImmediatePropagation();
}
function PasswordInput(props) { function PasswordInput(props) {
return ( return (
<TextInput <TextInput
{...props} {...props}
onCopy={onCopy}
/> />
); );
} }
@@ -6,7 +6,7 @@ import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel'; import FormLabel from 'Components/Form/FormLabel';
import { inputTypes } from 'Helpers/Props'; import { inputTypes } from 'Helpers/Props';
function getType(type, value) { function getType(type) {
switch (type) { switch (type) {
case 'captcha': case 'captcha':
return inputTypes.CAPTCHA; return inputTypes.CAPTCHA;
@@ -45,8 +45,7 @@ function getSelectValues(selectOptions) {
return _.reduce(selectOptions, (result, option) => { return _.reduce(selectOptions, (result, option) => {
result.push({ result.push({
key: option.value, key: option.value,
value: option.name, value: option.name
hint: option.hint
}); });
return result; return result;
@@ -63,7 +62,6 @@ function ProviderFieldFormGroup(props) {
value, value,
type, type,
advanced, advanced,
requestAction,
hidden, hidden,
pending, pending,
errors, errors,
@@ -88,7 +86,7 @@ function ProviderFieldFormGroup(props) {
<FormLabel>{label}</FormLabel> <FormLabel>{label}</FormLabel>
<FormInputGroup <FormInputGroup
type={getType(type, value)} type={getType(type)}
name={name} name={name}
label={label} label={label}
helpText={helpText} helpText={helpText}
@@ -100,7 +98,6 @@ function ProviderFieldFormGroup(props) {
pending={pending} pending={pending}
includeFiles={type === 'filePath' ? true : undefined} includeFiles={type === 'filePath' ? true : undefined}
onChange={onChange} onChange={onChange}
requestAction={requestAction}
{...otherProps} {...otherProps}
/> />
</FormGroup> </FormGroup>
@@ -121,7 +118,6 @@ ProviderFieldFormGroup.propTypes = {
value: PropTypes.any, value: PropTypes.any,
type: PropTypes.string.isRequired, type: PropTypes.string.isRequired,
advanced: PropTypes.bool.isRequired, advanced: PropTypes.bool.isRequired,
requestAction: PropTypes.string,
hidden: PropTypes.string, hidden: PropTypes.string,
pending: PropTypes.bool.isRequired, pending: PropTypes.bool.isRequired,
errors: PropTypes.arrayOf(PropTypes.object).isRequired, errors: PropTypes.arrayOf(PropTypes.object).isRequired,
@@ -15,12 +15,10 @@
.value { .value {
display: flex; display: flex;
max-width: 500px;
} }
.movieFolder { .movieFolder {
@add-mixin truncate; flex: 0 0 auto;
color: $disabledColor; color: $disabledColor;
} }
-19
View File
@@ -1,19 +0,0 @@
.input {
composes: input from '~Components/Form/Input.css';
flex-grow: 1;
min-height: 200px;
resize: vertical;
}
.readOnly {
background-color: #eee;
}
.hasError {
composes: hasError from '~Components/Form/Input.css';
}
.hasWarning {
composes: hasWarning from '~Components/Form/Input.css';
}
-172
View File
@@ -1,172 +0,0 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import styles from './TextArea.css';
class TextArea extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this._input = null;
this._selectionStart = null;
this._selectionEnd = null;
this._selectionTimeout = null;
this._isMouseTarget = false;
}
componentDidMount() {
window.addEventListener('mouseup', this.onDocumentMouseUp);
}
componentWillUnmount() {
window.removeEventListener('mouseup', this.onDocumentMouseUp);
if (this._selectionTimeout) {
this._selectionTimeout = clearTimeout(this._selectionTimeout);
}
}
//
// Control
setInputRef = (ref) => {
this._input = ref;
}
selectionChange() {
if (this._selectionTimeout) {
this._selectionTimeout = clearTimeout(this._selectionTimeout);
}
this._selectionTimeout = setTimeout(() => {
const selectionStart = this._input.selectionStart;
const selectionEnd = this._input.selectionEnd;
const selectionChanged = (
this._selectionStart !== selectionStart ||
this._selectionEnd !== selectionEnd
);
this._selectionStart = selectionStart;
this._selectionEnd = selectionEnd;
if (this.props.onSelectionChange && selectionChanged) {
this.props.onSelectionChange(selectionStart, selectionEnd);
}
}, 10);
}
//
// Listeners
onChange = (event) => {
const {
name,
onChange
} = this.props;
const payload = {
name,
value: event.target.value
};
onChange(payload);
}
onFocus = (event) => {
if (this.props.onFocus) {
this.props.onFocus(event);
}
this.selectionChange();
}
onKeyUp = () => {
this.selectionChange();
}
onMouseDown = () => {
this._isMouseTarget = true;
}
onMouseUp = () => {
this.selectionChange();
}
onDocumentMouseUp = () => {
if (this._isMouseTarget) {
this.selectionChange();
}
this._isMouseTarget = false;
}
//
// Render
render() {
const {
className,
readOnly,
autoFocus,
placeholder,
name,
value,
hasError,
hasWarning,
onBlur
} = this.props;
return (
<textarea
ref={this.setInputRef}
readOnly={readOnly}
autoFocus={autoFocus}
placeholder={placeholder}
className={classNames(
className,
readOnly && styles.readOnly,
hasError && styles.hasError,
hasWarning && styles.hasWarning
)}
name={name}
value={value}
onChange={this.onChange}
onFocus={this.onFocus}
onBlur={onBlur}
onKeyUp={this.onKeyUp}
onMouseDown={this.onMouseDown}
onMouseUp={this.onMouseUp}
/>
);
}
}
TextArea.propTypes = {
className: PropTypes.string.isRequired,
readOnly: PropTypes.bool,
autoFocus: PropTypes.bool,
placeholder: PropTypes.string,
name: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.array]).isRequired,
hasError: PropTypes.bool,
hasWarning: PropTypes.bool,
onChange: PropTypes.func.isRequired,
onFocus: PropTypes.func,
onBlur: PropTypes.func,
onSelectionChange: PropTypes.func
};
TextArea.defaultProps = {
className: styles.input,
type: 'text',
readOnly: false,
autoFocus: false,
value: ''
};
export default TextArea;
+1 -5
View File
@@ -130,8 +130,7 @@ class TextInput extends Component {
step, step,
min, min,
max, max,
onBlur, onBlur
onCopy
} = this.props; } = this.props;
return ( return (
@@ -156,8 +155,6 @@ class TextInput extends Component {
onChange={this.onChange} onChange={this.onChange}
onFocus={this.onFocus} onFocus={this.onFocus}
onBlur={onBlur} onBlur={onBlur}
onCopy={onCopy}
onCut={onCopy}
onKeyUp={this.onKeyUp} onKeyUp={this.onKeyUp}
onMouseDown={this.onMouseDown} onMouseDown={this.onMouseDown}
onMouseUp={this.onMouseUp} onMouseUp={this.onMouseUp}
@@ -183,7 +180,6 @@ TextInput.propTypes = {
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
onFocus: PropTypes.func, onFocus: PropTypes.func,
onBlur: PropTypes.func, onBlur: PropTypes.func,
onCopy: PropTypes.func,
onSelectionChange: PropTypes.func onSelectionChange: PropTypes.func
}; };
+7 -11
View File
@@ -4,17 +4,14 @@ import Icon from 'Components/Icon';
import { icons } from 'Helpers/Props'; import { icons } from 'Helpers/Props';
import styles from './HeartRating.css'; import styles from './HeartRating.css';
function HeartRating({ rating, iconSize, hideHeart }) { function HeartRating({ rating, iconSize }) {
return ( return (
<span> <span>
{ <Icon
!hideHeart && className={styles.heart}
<Icon name={icons.HEART}
className={styles.heart} size={iconSize}
name={icons.HEART} />
size={iconSize}
/>
}
{rating * 10}% {rating * 10}%
</span> </span>
@@ -23,8 +20,7 @@ function HeartRating({ rating, iconSize, hideHeart }) {
HeartRating.propTypes = { HeartRating.propTypes = {
rating: PropTypes.number.isRequired, rating: PropTypes.number.isRequired,
iconSize: PropTypes.number.isRequired, iconSize: PropTypes.number.isRequired
hideHeart: PropTypes.bool
}; };
HeartRating.defaultProps = { HeartRating.defaultProps = {
@@ -1,3 +0,0 @@
.lists {
flex: 1 0 auto;
}
-43
View File
@@ -1,43 +0,0 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React from 'react';
import { kinds, sizes } from 'Helpers/Props';
import Label from './Label';
import styles from './ImportListList.css';
function ImportListList({ lists, importListList }) {
return (
<div className={styles.lists}>
{
lists.map((t) => {
const list = _.find(importListList, { id: t });
if (!list) {
return null;
}
return (
<Label
key={list.id}
kind={kinds.INFO}
size={sizes.MEDIUM}
>
{list.name}
</Label>
);
})
}
</div>
);
}
ImportListList.propTypes = {
lists: PropTypes.arrayOf(PropTypes.number).isRequired,
importListList: PropTypes.arrayOf(PropTypes.object).isRequired
};
ImportListList.defaultProps = {
lists: []
};
export default ImportListList;
@@ -1,17 +0,0 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createImportListSelector from 'Store/Selectors/createImportListSelector';
import ImportListList from './ImportListList';
function createMapStateToProps() {
return createSelector(
createImportListSelector(),
(importListList) => {
return {
importListList
};
}
);
}
export default connect(createMapStateToProps)(ImportListList);
-9
View File
@@ -87,15 +87,6 @@
} }
} }
.queue {
border-color: $queueColor;
background-color: $queueColor;
&.outline {
color: $queueColor;
}
}
/** Sizes **/ /** Sizes **/
.small { .small {
@@ -17,7 +17,6 @@ class ClipboardButton extends Component {
this._id = getUniqueElememtId(); this._id = getUniqueElememtId();
this._successTimeout = null; this._successTimeout = null;
this._testResultTimeout = null;
this.state = { this.state = {
showSuccess: false, showSuccess: false,
@@ -27,8 +26,7 @@ class ClipboardButton extends Component {
componentDidMount() { componentDidMount() {
this._clipboard = new Clipboard(`#${this._id}`, { this._clipboard = new Clipboard(`#${this._id}`, {
text: () => this.props.value, text: () => this.props.value
container: document.getElementById(this._id)
}); });
this._clipboard.on('success', this.onSuccess); this._clipboard.on('success', this.onSuccess);
@@ -49,10 +47,6 @@ class ClipboardButton extends Component {
if (this._clipboard) { if (this._clipboard) {
this._clipboard.destroy(); this._clipboard.destroy();
} }
if (this._testResultTimeout) {
clearTimeout(this._testResultTimeout);
}
} }
// //
@@ -86,7 +80,6 @@ class ClipboardButton extends Component {
render() { render() {
const { const {
value, value,
className,
...otherProps ...otherProps
} = this.props; } = this.props;
@@ -102,7 +95,7 @@ class ClipboardButton extends Component {
return ( return (
<FormInputButton <FormInputButton
id={this._id} id={this._id}
className={className} className={styles.button}
{...otherProps} {...otherProps}
> >
<span className={showStateIcon ? styles.showStateIcon : undefined}> <span className={showStateIcon ? styles.showStateIcon : undefined}>
@@ -128,12 +121,7 @@ class ClipboardButton extends Component {
} }
ClipboardButton.propTypes = { ClipboardButton.propTypes = {
className: PropTypes.string.isRequired,
value: PropTypes.string.isRequired value: PropTypes.string.isRequired
}; };
ClipboardButton.defaultProps = {
className: styles.button
};
export default ClipboardButton; export default ClipboardButton;
@@ -1,4 +1,3 @@
import classNames from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import styles from './LoadingIndicator.css'; import styles from './LoadingIndicator.css';
@@ -14,7 +13,7 @@ function LoadingIndicator({ className, rippleClassName, size }) {
style={{ height }} style={{ height }}
> >
<div <div
className={classNames(styles.rippleContainer, 'followingBalls')} className={styles.rippleContainer}
style={{ width, height }} style={{ width, height }}
> >
<div <div
@@ -2,24 +2,8 @@ import React from 'react';
import styles from './LoadingMessage.css'; import styles from './LoadingMessage.css';
const messages = [ const messages = [
'Downloading more RAM', 'Welcome to Radarr Aphrodite Preview. Enjoy'
'Now in Technicolor', // TODO Add some messages here
'Previously on Radarr...',
'Bleep Bloop.',
'Locating the required gigapixels to render...',
'Spinning up the hamster wheel...',
'At least you\'re not on hold',
'Hum something loud while others stare',
'Loading humorous message... Please Wait',
'I could\'ve been faster in Python',
'Don\'t forget to rewind your tracks',
'Congratulations! you are the 1000th visitor.',
'HELP! I\'m being held hostage and forced to write these stupid lines!',
'RE-calibrating the internet...',
'I\'ll be here all week',
'Don\'t forget to tip your waitress',
'Apply directly to the forehead',
'Loading Battlestation'
]; ];
let message = null; let message = null;
+2 -4
View File
@@ -9,7 +9,6 @@ class Marquee extends Component {
static propTypes = { static propTypes = {
text: PropTypes.string, text: PropTypes.string,
title: PropTypes.string,
hoverToStop: PropTypes.bool, hoverToStop: PropTypes.bool,
loop: PropTypes.bool, loop: PropTypes.bool,
className: PropTypes.string className: PropTypes.string
@@ -17,7 +16,6 @@ class Marquee extends Component {
static defaultProps = { static defaultProps = {
text: '', text: '',
title: '',
hoverToStop: true, hoverToStop: true,
loop: false loop: false
}; };
@@ -146,7 +144,7 @@ class Marquee extends Component {
this.text = el; this.text = el;
}} }}
style={style} style={style}
title={(this.props.title && (this.props.text !== this.props.title)) ? `Original Title: ${this.props.title}` : this.props.text} title={this.props.text}
> >
{this.props.text} {this.props.text}
</span> </span>
@@ -169,7 +167,7 @@ class Marquee extends Component {
this.text = el; this.text = el;
}} }}
style={style} style={style}
title={(this.props.title && (this.props.text !== this.props.title)) ? `Original Title: ${this.props.title}` : this.props.text} title={this.props.text}
> >
{this.props.text} {this.props.text}
</span> </span>
+1 -2
View File
@@ -6,7 +6,6 @@ import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent'; import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter'; import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader'; import ModalHeader from 'Components/Modal/ModalHeader';
import translate from 'Utilities/String/translate';
import styles from './ModalError.css'; import styles from './ModalError.css';
function ModalError(props) { function ModalError(props) {
@@ -34,7 +33,7 @@ function ModalError(props) {
<Button <Button
onPress={onModalClose} onPress={onModalClose}
> >
{translate('Close')} Close
</Button> </Button>
</ModalFooter> </ModalFooter>
</ModalContent>); </ModalContent>);
+1 -2
View File
@@ -1,12 +1,11 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import PageContent from 'Components/Page/PageContent'; import PageContent from 'Components/Page/PageContent';
import translate from 'Utilities/String/translate';
import styles from './NotFound.css'; import styles from './NotFound.css';
function NotFound({ message }) { function NotFound({ message }) {
return ( return (
<PageContent title={translate('MIA')}> <PageContent title="MIA">
<div className={styles.container}> <div className={styles.container}>
<div className={styles.message}> <div className={styles.message}>
{message} {message}
@@ -6,7 +6,6 @@ import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent'; import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter'; import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader'; import ModalHeader from 'Components/Modal/ModalHeader';
import translate from 'Utilities/String/translate';
import styles from './KeyboardShortcutsModalContent.css'; import styles from './KeyboardShortcutsModalContent.css';
function getShortcuts() { function getShortcuts() {
@@ -20,26 +19,18 @@ function getShortcuts() {
} }
function getShortcutKey(combo, isOsx) { function getShortcutKey(combo, isOsx) {
const comboMatch = combo.match(/(.+?)\+(.*)/); const comboMatch = combo.match(/(.+?)\+(.)/);
if (!comboMatch) { if (!comboMatch) {
return combo; return combo;
} }
const modifier = comboMatch[1]; const modifier = comboMatch[1];
let key = comboMatch[2]; const key = comboMatch[2];
let osModifier = modifier; let osModifier = modifier;
if (modifier === 'mod') { if (modifier === 'mod') {
osModifier = isOsx ? 'cmd' : 'Ctrl'; osModifier = isOsx ? 'cmd' : 'ctrl';
}
if (key === 'home') {
key = isOsx ? '↑' : 'Home';
}
if (key === 'end') {
key = isOsx ? '↓' : 'End';
} }
return `${osModifier} + ${key}`; return `${osModifier} + ${key}`;
@@ -84,7 +75,7 @@ function KeyboardShortcutsModalContent(props) {
<Button <Button
onPress={onModalClose} onPress={onModalClose}
> >
{translate('Close')} Close
</Button> </Button>
</ModalFooter> </ModalFooter>
</ModalContent> </ModalContent>
@@ -4,15 +4,15 @@
} }
.loading { .loading {
position: absolute; margin-top: 18px;
display: inline-block; margin-bottom: 18px;
margin-left: 5px; text-align: center;
} }
.ripple { .ripple {
composes: ripple from '~Components/Loading/LoadingIndicator.css'; composes: ripple from '~Components/Loading/LoadingIndicator.css';
border: 1px solid $toolbarColor; border: 2px solid $toolbarColor;
} }
.input { .input {
@@ -11,7 +11,9 @@ import FuseWorker from './fuse.worker';
import MovieSearchResult from './MovieSearchResult'; import MovieSearchResult from './MovieSearchResult';
import styles from './MovieSearchInput.css'; import styles from './MovieSearchInput.css';
const LOADING_TYPE = 'suggestionsLoading';
const ADD_NEW_TYPE = 'addNew'; const ADD_NEW_TYPE = 'addNew';
const workerInstance = new FuseWorker();
class MovieSearchInput extends Component { class MovieSearchInput extends Component {
@@ -22,7 +24,6 @@ class MovieSearchInput extends Component {
super(props, context); super(props, context);
this._autosuggest = null; this._autosuggest = null;
this._worker = null;
this.state = { this.state = {
value: '', value: '',
@@ -32,23 +33,7 @@ class MovieSearchInput extends Component {
componentDidMount() { componentDidMount() {
this.props.bindShortcut(shortcuts.MOVIE_SEARCH_INPUT.key, this.focusInput); this.props.bindShortcut(shortcuts.MOVIE_SEARCH_INPUT.key, this.focusInput);
} workerInstance.addEventListener('message', this.onSuggestionsReceived, false);
componentWillUnmount() {
if (this._worker) {
this._worker.removeEventListener('message', this.onSuggestionsReceived, false);
this._worker.terminate();
this._worker = null;
}
}
getWorker() {
if (!this._worker) {
this._worker = new FuseWorker();
this._worker.addEventListener('message', this.onSuggestionsReceived, false);
}
return this._worker;
} }
// //
@@ -71,15 +56,6 @@ class MovieSearchInput extends Component {
return ( return (
<div className={styles.sectionTitle}> <div className={styles.sectionTitle}>
{section.title} {section.title}
{
section.loading &&
<LoadingIndicator
className={styles.loading}
rippleClassName={styles.ripple}
size={20}
/>
}
</div> </div>
); );
} }
@@ -97,6 +73,16 @@ class MovieSearchInput extends Component {
); );
} }
if (item.type === LOADING_TYPE) {
return (
<LoadingIndicator
className={styles.loading}
rippleClassName={styles.ripple}
size={30}
/>
);
}
return ( return (
<MovieSearchResult <MovieSearchResult
{...item.item} {...item.item}
@@ -113,8 +99,7 @@ class MovieSearchInput extends Component {
reset() { reset() {
this.setState({ this.setState({
value: '', value: '',
suggestions: [], suggestions: []
loading: false
}); });
} }
@@ -130,15 +115,6 @@ class MovieSearchInput extends Component {
} }
onKeyDown = (event) => { onKeyDown = (event) => {
if (event.shiftKey || event.altKey || event.ctrlKey) {
return;
}
if (event.key === 'Escape') {
this.reset();
return;
}
if (event.key !== 'Tab' && event.key !== 'Enter') { if (event.key !== 'Tab' && event.key !== 'Enter') {
return; return;
} }
@@ -179,74 +155,35 @@ class MovieSearchInput extends Component {
} }
onSuggestionsFetchRequested = ({ value }) => { onSuggestionsFetchRequested = ({ value }) => {
if (!this.state.loading) { this.setState({
this.setState({ suggestions: [
loading: true {
}); type: LOADING_TYPE,
} title: value
}
]
});
this.requestSuggestions(value); this.requestSuggestions(value);
}; };
requestSuggestions = _.debounce((value) => { requestSuggestions = _.debounce((value) => {
if (!this.state.loading) { const payload = {
return; value,
} movies: this.props.movies
};
const requestLoading = this.state.requestLoading; workerInstance.postMessage(payload);
this.setState({
requestValue: value,
requestLoading: true
});
if (!requestLoading) {
const payload = {
value,
movies: this.props.movies
};
this.getWorker().postMessage(payload);
}
}, 250); }, 250);
onSuggestionsReceived = (message) => { onSuggestionsReceived = (message) => {
const { this.setState({
value, suggestions: message.data
suggestions });
} = message.data;
if (!this.state.loading) {
this.setState({
requestValue: null,
requestLoading: false
});
} else if (value === this.state.requestValue) {
this.setState({
suggestions,
requestValue: null,
requestLoading: false,
loading: false
});
} else {
this.setState({
suggestions,
requestLoading: true
});
const payload = {
value: this.state.requestValue,
movies: this.props.movies
};
this.getWorker().postMessage(payload);
}
} }
onSuggestionsClearRequested = () => { onSuggestionsClearRequested = () => {
this.setState({ this.setState({
suggestions: [], suggestions: []
loading: false
}); });
} }
@@ -264,22 +201,20 @@ class MovieSearchInput extends Component {
render() { render() {
const { const {
value, value,
loading,
suggestions suggestions
} = this.state; } = this.state;
const suggestionGroups = []; const suggestionGroups = [];
if (suggestions.length || loading) { if (suggestions.length) {
suggestionGroups.push({ suggestionGroups.push({
title: translate('ExistingMovies'), title: 'Existing Movie',
loading,
suggestions suggestions
}); });
} }
suggestionGroups.push({ suggestionGroups.push({
title: translate('AddNewMovie'), title: 'Add New Movie',
suggestions: [ suggestions: [
{ {
type: ADD_NEW_TYPE, type: ADD_NEW_TYPE,
@@ -21,9 +21,8 @@
} }
.logoFull { .logoFull {
margin-left: 15px; width: 144px;
width: 120px; height: 48px;
height: 40px;
} }
.logo { .logo {
@@ -4,7 +4,6 @@ import keyboardShortcuts, { shortcuts } from 'Components/keyboardShortcuts';
import IconButton from 'Components/Link/IconButton'; import IconButton from 'Components/Link/IconButton';
import Link from 'Components/Link/Link'; import Link from 'Components/Link/Link';
import { icons } from 'Helpers/Props'; import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import KeyboardShortcutsModal from './KeyboardShortcutsModal'; import KeyboardShortcutsModal from './KeyboardShortcutsModal';
import MovieSearchInputConnector from './MovieSearchInputConnector'; import MovieSearchInputConnector from './MovieSearchInputConnector';
import PageHeaderActionsMenuConnector from './PageHeaderActionsMenuConnector'; import PageHeaderActionsMenuConnector from './PageHeaderActionsMenuConnector';
@@ -80,7 +79,7 @@ class PageHeader extends Component {
/> />
<IconButton <IconButton
className={styles.translate} className={styles.translate}
title={translate('SuggestTranslationChange')} title="Suggest translation change"
name={icons.TRANSLATE} name={icons.TRANSLATE}
to="https://translate.servarr.com/projects/radarr/radarr/" to="https://translate.servarr.com/projects/radarr/radarr/"
size={24} size={24}
@@ -7,7 +7,6 @@ import MenuContent from 'Components/Menu/MenuContent';
import MenuItem from 'Components/Menu/MenuItem'; import MenuItem from 'Components/Menu/MenuItem';
import MenuItemSeparator from 'Components/Menu/MenuItemSeparator'; import MenuItemSeparator from 'Components/Menu/MenuItemSeparator';
import { align, icons, kinds } from 'Helpers/Props'; import { align, icons, kinds } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './PageHeaderActionsMenu.css'; import styles from './PageHeaderActionsMenu.css';
function PageHeaderActionsMenu(props) { function PageHeaderActionsMenu(props) {
@@ -33,7 +32,7 @@ function PageHeaderActionsMenu(props) {
className={styles.itemIcon} className={styles.itemIcon}
name={icons.KEYBOARD} name={icons.KEYBOARD}
/> />
{translate('KeyboardShortcuts')} Keyboard Shortcuts
</MenuItem> </MenuItem>
<MenuItemSeparator /> <MenuItemSeparator />
@@ -43,7 +42,7 @@ function PageHeaderActionsMenu(props) {
className={styles.itemIcon} className={styles.itemIcon}
name={icons.RESTART} name={icons.RESTART}
/> />
{translate('Restart')} Restart
</MenuItem> </MenuItem>
<MenuItem onPress={onShutdownPress}> <MenuItem onPress={onShutdownPress}>
@@ -52,7 +51,7 @@ function PageHeaderActionsMenu(props) {
name={icons.SHUTDOWN} name={icons.SHUTDOWN}
kind={kinds.DANGER} kind={kinds.DANGER}
/> />
{translate('Shutdown')} Shutdown
</MenuItem> </MenuItem>
{ {

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